Add mdview markdown viewer, reorganize tests/examples and libc layout
- Split tests/ (libc feature tests) and examples/ (real apps); shared
app.mk in repo root, was examples/example.mk
- libc/io/* split into libc/{conio,env,errno,file,mouse,string,sys,
time,video}/ — clearer module boundaries
- New examples/mdview/: markdown viewer (Phases 1-5 + light nested
lists). Headers (H1-H4), HR, ulist/olist/quote with nesting via
leading spaces, fenced code blocks, inline emphasis (bold/italic/
underscore/code), wrap/unwrap mode with soft wrap (F2), horizontal
pan (← →) with '>' truncation indicator
- libc additions: scroll() in conio (ESTEX SCROLL), strlwr/strupr,
gets() test
- Makefile updates across tests/ for the new shared app.mk path
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,30 @@ char getche(void) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* getkey — like getch() but exposes BOTH the ASCII value and the
|
||||
* positional scan code, so callers can distinguish extended keys
|
||||
* (arrows, F1..F12, PgUp/PgDn, Home/End, Ins/Del — all of which carry
|
||||
* ASCII == 0 from ESTEX) from plain ASCII keys.
|
||||
*
|
||||
* return = (scan << 8) | ascii
|
||||
*
|
||||
* For plain keys: ascii holds the character, scan holds the positional
|
||||
* code (bit 7 set when Ctrl/Alt/Shift is held).
|
||||
* For extended keys: ascii == 0, scan identifies the key (see KEY_* in
|
||||
* <conio.h>).
|
||||
*/
|
||||
uint16_t getkey(void) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII
|
||||
rst #0x10
|
||||
pop ix
|
||||
ld e, a ; E = ASCII (defensive: A is the canonical copy)
|
||||
ret ; __sdcccall(1) returns uint16_t in DE
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ---- putch / cputs: Turbo-C conio convention ---------------------- *
|
||||
* Both APPLY the current text attribute (g_text_attr). When attr is
|
||||
* KEEP_EXIST_ATTR (>0xFF), they short-circuit to the FAST stdio path
|
||||
@@ -462,6 +486,37 @@ uint16_t wherexy(void) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
||||
*
|
||||
* SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
|
||||
* packed and pushed on the stack as a single 16-bit value (caller does
|
||||
* `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL:
|
||||
* [SP+0..1] = return address
|
||||
* [SP+2] = ch (low half of pushed pair)
|
||||
* [SP+3] = attr (high half)
|
||||
* Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
|
||||
*/
|
||||
void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked
|
||||
{
|
||||
(void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear;
|
||||
__asm
|
||||
pop iy ; return address
|
||||
ld d, l ; D = row (y)
|
||||
ld e, a ; E = col (x)
|
||||
pop hl ; H = heigth(h), L = width(w)
|
||||
pop bc ; C = direction, B = clear
|
||||
ld a, b ; A = clear(B)
|
||||
ld b, c ; B = direction(C)
|
||||
push ix
|
||||
ld c, #0x55 ; ESTEX SCROLL
|
||||
rst #0x10
|
||||
pop ix
|
||||
jp (iy)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
||||
*
|
||||
Vendored
@@ -143,7 +143,8 @@ int fputc(int c, FILE *fp)
|
||||
{
|
||||
if (!fp) { errno = EBADF; return EOF; }
|
||||
if (fp->flags & _F_CONOUT) {
|
||||
return putchar(c);
|
||||
putchar(c);
|
||||
return (int)c;
|
||||
}
|
||||
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
|
||||
uint8_t ch = (uint8_t)c;
|
||||
@@ -175,7 +176,7 @@ int fputs(const char *s, FILE *fp)
|
||||
if (!fp || !s) { errno = EBADF; return EOF; }
|
||||
if (fp->flags & _F_CONOUT) {
|
||||
while (*s) {
|
||||
if (putchar((unsigned char)*s++) == EOF) return EOF;
|
||||
putchar((unsigned char)*s++);;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -239,7 +240,7 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp)
|
||||
const char *p = (const char *)ptr;
|
||||
size_t total = size * nmemb;
|
||||
for (size_t i = 0; i < total; i++) {
|
||||
if (putchar((unsigned char)p[i]) == EOF) return i / size;
|
||||
putchar((unsigned char)p[i]);
|
||||
}
|
||||
return nmemb;
|
||||
}
|
||||
+54
-4
@@ -38,6 +38,42 @@
|
||||
char kbhit (void);
|
||||
char getch (void);
|
||||
char getche(void);
|
||||
|
||||
/* Extended-key reader. Returns (scan << 8) | ascii. Plain ASCII keys
|
||||
* have ascii in the low byte; extended keys (arrows / F1..F12 /
|
||||
* PgUp/PgDn / Home / End / Ins / Del) carry ascii == 0 and the
|
||||
* KEY_* code in the high byte. */
|
||||
uint16_t getkey(void);
|
||||
|
||||
/* Scan codes returned in the high byte of getkey() when the low byte
|
||||
* (ASCII) is 0. Empirically verified in MAME — the ProgrammerManual.txt
|
||||
* "positional code" column is misleading; BIOS returns IBM-style codes
|
||||
* for the F-keys and a "5N + numpad-position" pattern for the cursor /
|
||||
* editing keys.
|
||||
*
|
||||
* Verified 2026-06-04 by reading raw getkey() output. */
|
||||
#define KEY_F1 0x3B
|
||||
#define KEY_F2 0x3C
|
||||
#define KEY_F3 0x3D
|
||||
#define KEY_F4 0x3E
|
||||
#define KEY_F5 0x3F
|
||||
#define KEY_F6 0x40
|
||||
#define KEY_F7 0x41
|
||||
#define KEY_F8 0x42
|
||||
#define KEY_F9 0x43
|
||||
#define KEY_F10 0x44
|
||||
#define KEY_F11 0x45 /* not verified */
|
||||
#define KEY_F12 0x46 /* not verified */
|
||||
#define KEY_END 0x51
|
||||
#define KEY_DOWN 0x52
|
||||
#define KEY_PGDN 0x53
|
||||
#define KEY_LEFT 0x54
|
||||
#define KEY_RIGHT 0x56
|
||||
#define KEY_HOME 0x57
|
||||
#define KEY_UP 0x58
|
||||
#define KEY_PGUP 0x59
|
||||
#define KEY_INS 0x50 /* numpad 0; not verified */
|
||||
#define KEY_DEL 0x55 /* numpad 5/.; not verified */
|
||||
char putch (char c);
|
||||
char cputs (const char *s);
|
||||
int cprintf(const char *fmt, ...);
|
||||
@@ -63,6 +99,8 @@ uint8_t wherex (void);
|
||||
uint8_t wherey (void);
|
||||
uint16_t wherexy(void); // high byte = Y, low byte = X coords.
|
||||
|
||||
void scroll(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
|
||||
|
||||
/* Direct character/attribute screen access (ESTEX $57 / $58).
|
||||
* wrchar — write char + attribute at (x, y); does NOT advance the cursor
|
||||
* and does NOT interpret control characters. Useful for
|
||||
@@ -143,10 +181,22 @@ uint8_t get_putch_raw_mode(void);
|
||||
* Colour order is standard CGA / Borland-conio.h. Constants 0..7 are
|
||||
* usable for both fg and bg; 8..15 are foreground-only. */
|
||||
enum {
|
||||
COLOR_BLACK = 0, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN,
|
||||
COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_LIGHTGRAY,
|
||||
COLOR_DARKGRAY, COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN,
|
||||
COLOR_LIGHTRED, COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE
|
||||
COLOR_BLACK = 0,
|
||||
COLOR_BLUE,
|
||||
COLOR_GREEN,
|
||||
COLOR_CYAN,
|
||||
COLOR_RED,
|
||||
COLOR_MAGENTA,
|
||||
COLOR_BROWN,
|
||||
COLOR_LIGHTGRAY,
|
||||
COLOR_DARKGRAY,
|
||||
COLOR_LIGHTBLUE,
|
||||
COLOR_LIGHTGREEN,
|
||||
COLOR_LIGHTCYAN,
|
||||
COLOR_LIGHTRED,
|
||||
COLOR_LIGHTMAGENTA,
|
||||
COLOR_YELLOW,
|
||||
COLOR_WHITE
|
||||
};
|
||||
#define COLOR_BLINK 0x80u
|
||||
#define COLOR(fg, bg) ((uint8_t)((((bg) & 0x07) << 4) | ((fg) & 0x0F)))
|
||||
|
||||
+15
-7
@@ -16,14 +16,22 @@
|
||||
#ifndef FCNTL_H
|
||||
#define FCNTL_H
|
||||
|
||||
#define O_RDONLY 0
|
||||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
#ifndef _STD_SEEK_
|
||||
#define _STD_SEEK_
|
||||
/* constants to be used as 3rd argument for "fseek" function */
|
||||
#define SEEK_SET 0
|
||||
#define SEEK_CUR 1
|
||||
#define SEEK_END 2
|
||||
#endif
|
||||
|
||||
#define O_CREAT 0x040
|
||||
#define O_EXCL 0x080
|
||||
#define O_TRUNC 0x200
|
||||
#define O_APPEND 0x400
|
||||
/* Definition "open flags" */
|
||||
#define O_WRONLY 0x01 /* 0 file write only */
|
||||
#define O_RDONLY 0x02 /* 1 file read only */
|
||||
#define O_RDWR 0x03 /* 1,0 file read/write */
|
||||
#define O_TRUNC 0x04 /* 2 open with truncation */
|
||||
#define O_CREAT 0x08 /* 3 create and open file */
|
||||
#define O_EXCL 0x10 /* 4 exclusive open */
|
||||
#define O_APPEND 0x20 /* 5 to end of file */
|
||||
|
||||
int open (const char *path, int flags);
|
||||
int creat(const char *path, int mode); /* mode arg ignored on Sprinter */
|
||||
|
||||
@@ -30,7 +30,7 @@ int vprintf(const char *, va_list);
|
||||
int vsprintf(char *, const char *, va_list);
|
||||
|
||||
/* puts / putchar / getchar — overridden by our libc to use ESTEX. */
|
||||
int puts (const char *);
|
||||
char puts (const char *);
|
||||
int putchar(int);
|
||||
int getchar(void);
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strlwr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if (*p >= 'A' && *p <= 'Z') *p += 'a' - 'A';
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
char *strupr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if (*p >= 'a' && *p <= 'z') *p -= 'a' - 'A';
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* div() comes from SDCC's z80.lib. */
|
||||
@@ -17,13 +17,7 @@ int getchar(void) __naked
|
||||
ld c, #0x30 ; ESTEX WAITKEY
|
||||
rst #0x10
|
||||
pop ix
|
||||
ld a, e ; E = ASCII (already the low byte of our return DE)
|
||||
or a, a
|
||||
jr Z, no_ascii
|
||||
ld d, #0
|
||||
ret
|
||||
no_ascii:
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
+6
-14
@@ -19,23 +19,15 @@ int putchar(int c) __naked
|
||||
{
|
||||
(void)c;
|
||||
__asm
|
||||
ld a, l ; SDCC __sdcccall(1) int → HL
|
||||
push ix
|
||||
ld a, l
|
||||
cp #0x0A
|
||||
jr nz, _pc_emit
|
||||
ld a, #0x0D ; CR before LF
|
||||
push af
|
||||
jr nz, cputc
|
||||
call cputc
|
||||
ld a, #0x0D
|
||||
cputc:
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
pop af
|
||||
ld a, #0x0A
|
||||
_pc_emit:
|
||||
push af
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
pop af
|
||||
pop ix
|
||||
ld e, a
|
||||
ld e, l
|
||||
ld d, #0
|
||||
ret
|
||||
__endasm;
|
||||
|
||||
+21
-34
@@ -14,17 +14,11 @@
|
||||
* - Avoid trailing PUTCHAR after PCHARS — empirically that sometimes
|
||||
* drops the next char. Embed the line ending inside the PCHARS
|
||||
* buffer instead.
|
||||
* - Strings longer than the buffer fall back to per-char putchar so
|
||||
* we never silently truncate.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PUTS_BUF_SIZE 256 /* body bytes before CR expansion */
|
||||
|
||||
static char puts_buf[PUTS_BUF_SIZE + 3]; /* +3 for trailing CR LF NUL */
|
||||
|
||||
static void pchars(const char *s) __naked
|
||||
{
|
||||
(void)s;
|
||||
@@ -37,33 +31,26 @@ static void pchars(const char *s) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
int puts(const char *s)
|
||||
char puts(const char *s) __naked
|
||||
{
|
||||
uint16_t n = 0;
|
||||
uint16_t i = 0;
|
||||
|
||||
while (s[i] && n < PUTS_BUF_SIZE - 1) {
|
||||
char c = s[i++];
|
||||
if (c == '\n') {
|
||||
puts_buf[n++] = '\r';
|
||||
puts_buf[n++] = '\n';
|
||||
} else {
|
||||
puts_buf[n++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (s[i]) {
|
||||
/* Overflow — char-by-char fallback so we never truncate. */
|
||||
for (uint16_t k = 0; s[k]; k++)
|
||||
putchar((unsigned char)s[k]);
|
||||
putchar('\n');
|
||||
return 0;
|
||||
}
|
||||
|
||||
puts_buf[n++] = '\r';
|
||||
puts_buf[n++] = '\n';
|
||||
puts_buf[n] = 0;
|
||||
|
||||
pchars(puts_buf);
|
||||
return 0;
|
||||
(void)s;
|
||||
__asm
|
||||
puts_:
|
||||
ld a, (hl)
|
||||
or a
|
||||
jr z, fin_
|
||||
push hl
|
||||
ld l, a
|
||||
ld h, #0
|
||||
call _putchar
|
||||
pop hl
|
||||
inc hl
|
||||
jp puts_
|
||||
;
|
||||
fin_:
|
||||
ld l, #0x0A
|
||||
ld h, #0
|
||||
call _putchar
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* solid_helpers.c — small Solid-C compatibility helpers.
|
||||
*
|
||||
* dec8 / dec16 / dec32 / hex8 / hex16 / hex32 are in dec_hex.c (compact
|
||||
* asm port from solid-c's STDLIB.ASM, ~150 bytes total — vs ~3-5 KB if
|
||||
* routed through printf). This file now only holds gets().
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* ---- gets — dangerous but Solid-C provides it ---------------------- */
|
||||
char *gets(char *buf)
|
||||
{
|
||||
int i = 0;
|
||||
int c;
|
||||
for (;;) {
|
||||
c = getchar();
|
||||
if (c == EOF) {
|
||||
if (i == 0) return 0;
|
||||
break;
|
||||
}
|
||||
if (c == '\n' || c == '\r') break;
|
||||
buf[i++] = (char)c;
|
||||
}
|
||||
buf[i] = 0;
|
||||
return buf;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*
|
||||
* CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase
|
||||
* conversion for both Latin and Cyrillic characters in CP866 code page
|
||||
* (bytes 0x80–0xFF).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strlwr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if ((*p >= 'A' && *p <= 'Z') || (*p >= 0x80 && *p <= 0x8F)) *p += 'a' - 'A';
|
||||
else if ((*p >= 0x90 && *p <= 0x9F)) *p += 0x50;
|
||||
else if ((*p == 0xF0)) *p = 0xF1;
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*
|
||||
* CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase
|
||||
* conversion for both Latin and Cyrillic characters in CP866 code page
|
||||
* (bytes 0x80–0xFF).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strupr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if ((*p >= 'a' && *p <= 'z') || (*p >= 0xA0 && *p <= 0xAF)) *p -= 'a' - 'A';
|
||||
else if ((*p >= 0xE0 && *p <= 0xEF)) *p -= 0x50;
|
||||
else if ((*p == 0xF1)) *p = 0xF0;
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
Reference in New Issue
Block a user