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:
2026-06-04 22:23:36 +03:00
parent b851e22fa6
commit 737c974400
104 changed files with 2485 additions and 223 deletions
+305
View File
@@ -0,0 +1,305 @@
/*
* mouse.c — Sprinter mouse driver wrappers (RST 30h).
*
* All calls use the same pattern as ESTEX (push/pop IX around the RST)
* since the driver doesn't promise to preserve registers either.
*/
#include <mouse.h>
/* Scratch for READ_STATE — RST 30h clobbers HL/DE/A so we can't keep the
* state pointer in HL across the call. Explicit `= 0` so SDCC reserves
* real BSS storage — see memory/sdcc_static_storage_gotcha.md. */
static uint16_t mb_x = 0, mb_y = 0;
static uint8_t mb_buttons = 0;
int mouse_init(void) __naked
{
__asm
push ix
ld c, #0x00 ; INITIALIZATION
rst #0x30 ; MOUSE
pop ix
jr c, _mi_err
ld de, #0
ret
_mi_err:
ld de, #-1
ret
__endasm;
}
void mouse_show(void) __naked
{
__asm
push ix
ld c, #0x01 ; SHOW MOUSE CURSOR
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
void mouse_hide(void) __naked
{
__asm
push ix
ld c, #0x02 ; HIDE MOUSE CURSOR
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
void mouse_refresh(void) __naked
{
__asm
push ix
ld c, #0x83 ; MOUSE REFRESH
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
void mouse_read(mouse_state_t *st) __naked
{
(void)st;
__asm
;; HL = state ptr on entry.
push ix
push hl ; stash ptr across RST
ld c, #0x03 ; READ MOUSE STATE
rst #0x30 ; MOUSE
;; Returns: A=buttons, HL=x, DE=y (CF=err but we ignore here)
ld (_mb_x), hl
ld (_mb_y), de
ld (_mb_buttons), a
pop hl ; restore state ptr
pop ix
;; Copy scratch *st. Struct layout: x(2), y(2), buttons(1).
ld de, (_mb_x)
ld (hl), e
inc hl
ld (hl), d
inc hl
ld de, (_mb_y)
ld (hl), e
inc hl
ld (hl), d
inc hl
ld a, (_mb_buttons)
ld (hl), a
ret
__endasm;
}
void mouse_goto(int x, int y) __naked
{
(void)x; (void)y;
__asm
;; HL = x, DE = y.
push ix
ld c, #0x04 ; GOTO MOUSE CURSOR
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
void mouse_bounds_x(int xmin, int xmax) __naked
{
(void)xmin; (void)xmax;
__asm
;; HL = xmin, DE = xmax.
push ix
ld c, #0x08 ; HORZ BOUNDS
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
void mouse_bounds_y(int ymin, int ymax) __naked
{
(void)ymin; (void)ymax;
__asm
;; HL = ymin, DE = ymax.
push ix
ld c, #0x07 ; VERT BOUNDS
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
/* ---- $09 LOAD CURSOR + $0B RETURN CURSOR ---------------------- */
/* Scratch for the IX-passing convention — RST 30h takes the bitmap
* pointer in IX, so we have to set it up explicitly.
*
* Initialised to 0 so SDCC reserves real BSS storage — uninitialised
* `static uint8_t` declarations can coalesce to a single address and
* stomp on each other. See memory/sdcc_static_storage_gotcha.md. */
static uint16_t mc_image = 0;
static uint8_t mc_width = 0;
static uint8_t mc_height = 0;
static uint8_t mc_hot_x = 0;
static uint8_t mc_hot_y = 0;
/* Saved struct pointer for mouse_get_cursor. SDCC __sdcccall(1) passes
* `c` in HL, then stashes it in DE around the inline asm. Our asm
* clobbers DE (driver returns hot_y/hot_x in D/E) so the post-asm
* `c->width = ...` writes would otherwise land at a garbage address.
* We park the pointer in BSS instead so SDCC re-fetches it from
* memory after the asm. */
static mouse_cursor_t *mc_dest = 0;
void mouse_load_cursor(const mouse_cursor_t *c)
{
/* Copy fields out of the C struct into our scratch globals so the
* asm side has well-known names. */
mc_image = (uint16_t)(uintptr_t)c->image;
mc_width = c->width;
mc_height = c->height;
mc_hot_x = c->hot_x;
mc_hot_y = c->hot_y;
__asm
push ix
ld ix, (_mc_image)
ld a, (_mc_height)
ld h, a
ld a, (_mc_width)
ld l, a
ld a, (_mc_hot_y)
ld d, a
ld a, (_mc_hot_x)
ld e, a
ld b, #0
ld c, #0x09 ; LOAD MOUSE CURSOR
rst #0x30 ; MOUSE
pop ix
__endasm;
}
void mouse_get_cursor(mouse_cursor_t *c)
{
mc_dest = c; /* park ptr in BSS */
mc_image = (uint16_t)(uintptr_t)c->image;
__asm
push ix
ld ix, (_mc_image) ; IX = bitmap buffer from caller
ld c, #0x0B ; RETURN CURSOR
rst #0x30 ; mouse driver, NOT ESTEX
;; Returns: H=height, L=width, D=hot_y, E=hot_x.
ld a, h
ld (_mc_height), a
ld a, l
ld (_mc_width), a
ld a, d
ld (_mc_hot_y), a
ld a, e
ld (_mc_hot_x), a
pop ix
__endasm;
/* Re-fetch the struct pointer from BSS — `c` (kept in DE by SDCC
* around the inline asm) was clobbered by the RST 30h above. */
mouse_cursor_t *p = mc_dest;
p->width = mc_width;
p->height = mc_height;
p->hot_x = mc_hot_x;
p->hot_y = mc_hot_y;
}
/* ---- $0E / $0F SENSITIVITY ------------------------------------ */
/* GET returns H=vert, L=horz in HL. We expose the two halves as
* separate getters so the simple "uint8_t" return ABI works cleanly. */
static int ms_query(void) __naked
{
__asm
push ix
ld c, #0x0E ; GET SENSITIVITY
rst #0x30 ; MOUSE
ld d, h
ld e, l
pop ix
ret
__endasm;
}
uint8_t mouse_get_sensitivity_x(void)
{
return (uint8_t)(ms_query() & 0xFF); /* E = horz */
}
uint8_t mouse_get_sensitivity_y(void)
{
return (uint8_t)(ms_query() >> 8); /* D = vert */
}
void mouse_set_sensitivity(uint8_t horz, uint8_t vert) __naked
{
(void)horz; (void)vert;
/* Pack into HL: H=vert, L=horz. */
__asm
push ix
ld h, l
ld l, a
ld c, #0x0F ; SET SENSITIVITY
rst #0x30 ; MOUSE
pop ix
__endasm;
}
/* ---- $81 CHANGE VIDEO MODE ------------------------------------ */
/* SDCC __sdcccall(1): single uint8_t arg arrives in A. */
void mouse_video_mode_changed(uint8_t mode) __naked
{
(void)mode;
__asm
push ix
;; A already holds the mode byte (from SDCC ABI).
ld c, #0x81 ; CHANGE VIDEO MODE
rst #0x30 ; MOUSE
pop ix
ret
__endasm;
}
/* CURSOR_TEXT_MODES ($0A):
* B = 0
* H = AND symbol mask L = XOR symbol mask
* D = AND attribute mask E = XOR attribute mask
*
* SDCC __sdcccall(1) gives us:
* sym_and in L (low byte of HL arg)
* sym_xor in E (low byte of DE arg)
* attr_and at [SP+2]
* attr_xor at [SP+3]
*/
void mouse_text_cursor(uint8_t sym_and, uint8_t sym_xor,
uint8_t attr_and, uint8_t attr_xor) __naked
{
(void)sym_and; (void)sym_xor; (void)attr_and; (void)attr_xor;
__asm
;; SDCC __sdcccall(1) for 4×uint8_t args:
;; arg1 sym_and A
;; arg2 sym_xor L
;; arg3 attr_and stack low byte (packed into HL.L on caller, push HL)
;; arg4 attr_xor stack high byte (HL.H pushed by caller)
pop iy ; return address
pop bc ; C = attr_and (low), B = attr_xor (high)
push ix
;; Target: H=sym_and, L=sym_xor, D=attr_and, E=attr_xor, B=0
ld h, a ; H = sym_and (from A)
; L already holds sym_xor
ld d, c ; D = attr_and
ld e, b ; E = attr_xor
ld b, #0
ld c, #0x0A ; CURSOR TEXT MODE
rst #0x30 ; MOUSE
pop ix
jp (iy)
__endasm;
}