joeylib2/tests/gate/pspKeyboardPath.c

425 lines
13 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// pspKeyboardPath.c - the PSP keyboard's d-pad moves and presses for some
// typing, for a gate to send the PSP.
//
// pspKeyboardPath SOURCE TOKEN...
//
// The keyboard (src/hosted/psp/pspKeyboard.c) is read from its own source:
// its rows of KEY_* entries, each key's width in half keys, what it types,
// and its legend, and where the cursor starts. Its cursor is followed as
// the keyboard moves it -- along a row wrapping round, and up or down a row
// onto the key nearest the middle of the one it was on (ties: down-right
// going down, up-left going up). Each step is printed on a line of its own:
//
// move left|right|up|down one d-pad press
// press LEGEND cross on the key with that legend
//
// A TOKEN is "key:LEGEND" (a key by its legend: ESC, F5, SHIFT, ENTER...)
// or "type:TEXT" (each character of TEXT, Shift pressed first for a
// shifted one, which the keyboard lets go after the next key).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "toolFile.h"
#define TOOL_NAME "pspKeyboardPath"
#define ROWS_MAX 8u
#define KEYS_MAX 24u
#define LEGEND_MAX 32u
#define ARGS_MAX 6u
#define ARG_MAX 64u
#define NO_CHAR (-1)
#define LETTER_HALVES 2u
#define DECIMAL 10
#define SHIFT_LEGEND "SHIFT"
#define ROW_START "#define CURSOR_ROW_START"
#define COL_START "#define CURSOR_COL_START"
#define ROW_OPEN "static const BoardKeyT gRow"
#define ROW_OPEN_TAIL "[] = {"
#define ROW_CLOSE "\n};"
// A key: its width in half keys, its legend, and what it types plain and
// shifted (NO_CHAR for nothing).
typedef struct KeyT {
uint32_t halves;
char legend[LEGEND_MAX];
int plain;
int shifted;
} KeyT;
typedef struct RowT {
KeyT keys[KEYS_MAX];
uint32_t count;
} RowT;
typedef struct BoardT {
RowT rows[ROWS_MAX];
uint32_t count;
uint32_t row;
uint32_t col;
} BoardT;
// The cast charOf looks through.
static const char kCast[] = "(uint8_t)(";
static uint32_t argsSplit(const char *call, size_t len, char out[ARGS_MAX][ARG_MAX]);
static uint32_t centre(const RowT *row, uint32_t col);
static int charOf(const char *text);
static uint32_t defineOf(const char *text, const char *name);
static void find(const BoardT *board, const char *want, bool byLegend, uint32_t *row, uint32_t *col, bool *shifted);
static void keyAdd(RowT *row, const char *kind, size_t kindLen, char a[ARGS_MAX][ARG_MAX]);
static void legendCopy(char *legend, const char *text);
static int mod(int a, int n);
static void parse(BoardT *board, const char *text);
static void path(BoardT *board, uint32_t toRow, uint32_t toCol);
static void press(BoardT *board, const char *want, bool byLegend);
// A KEY_*(...) entry's arguments, split at its top-level commas and
// trimmed.
static uint32_t argsSplit(const char *call, size_t len, char out[ARGS_MAX][ARG_MAX]) {
uint32_t count;
size_t n;
size_t i;
int depth;
char quote;
char c;
count = 0;
n = 0;
depth = 0;
quote = 0;
for (i = 0; i <= len && count < ARGS_MAX; i++) {
c = i < len ? call[i] : ',';
if (quote != 0) {
if (c == '\\' && i + 1 < len && n + 2 < ARG_MAX) {
out[count][n++] = c;
c = call[++i];
} else if (c == quote) {
quote = 0;
}
} else if (c == '\'' || c == '"') {
quote = c;
} else if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
} else if (c == ',' && depth == 0) {
while (n > 0 && (out[count][n - 1u] == ' ' || out[count][n - 1u] == '\t')) {
n--;
}
out[count][n] = 0;
count++;
n = 0;
continue;
}
if ((c == ' ' || c == '\t') && n == 0) {
continue;
}
if (n + 1 < ARG_MAX) {
out[count][n++] = c;
}
}
return count;
}
// Twice the middle of a key, in half keys.
static uint32_t centre(const RowT *row, uint32_t col) {
uint32_t start;
uint32_t i;
start = 0;
for (i = 0; i < col; i++) {
start += row->keys[i].halves;
}
return 2u * start + row->keys[col].halves;
}
// A C character literal's character ('\'' and '\\' unescaped), or NO_CHAR
// when the text is not one; a (uint8_t)(...) cast round it is looked
// through.
static int charOf(const char *text) {
char inner[ARG_MAX];
size_t len;
len = strlen(text);
if (strncmp(text, kCast, sizeof(kCast) - 1u) == 0 && len > sizeof(kCast) && text[len - 1u] == ')') {
len -= sizeof(kCast);
memcpy(inner, text + sizeof(kCast) - 1u, len);
inner[len] = 0;
return charOf(inner);
}
if (len < 3u || text[0] != '\'' || text[len - 1u] != '\'') {
return NO_CHAR;
}
return (uint8_t)(text[1] == '\\' ? text[2] : text[1]);
}
// The number a "#define NAME 12u" gives.
static uint32_t defineOf(const char *text, const char *name) {
const char *at;
at = strstr(text, name);
if (at == NULL) {
toolFail("no %s in the keyboard's source", name);
}
return (uint32_t)strtoul(at + strlen(name), NULL, DECIMAL);
}
// The key with the legend want, or else the one that types want (shifted
// when only its shifted character is it).
static void find(const BoardT *board, const char *want, bool byLegend, uint32_t *row, uint32_t *col, bool *shifted) {
uint32_t r;
uint32_t c;
int pass;
int got;
for (pass = 0; pass < 2; pass++) {
for (r = 0; r < board->count; r++) {
for (c = 0; c < board->rows[r].count; c++) {
got = pass == 0 ? board->rows[r].keys[c].plain : board->rows[r].keys[c].shifted;
if ((byLegend && pass == 0 && strcmp(board->rows[r].keys[c].legend, want) == 0) || (!byLegend && got != NO_CHAR && got == (uint8_t)want[0])) {
*row = r;
*col = c;
*shifted = pass == 1;
return;
}
}
}
}
if (byLegend) {
toolFail("no key shows or types '%s'", want);
}
toolFail("no key shows or types '%c'", want[0]);
}
// One KEY_* entry as a key.
static void keyAdd(RowT *row, const char *kind, size_t kindLen, char a[ARGS_MAX][ARG_MAX]) {
KeyT *key;
if (row->count == KEYS_MAX) {
toolFail("a keyboard row has more than %u keys", KEYS_MAX);
}
key = &row->keys[row->count];
if (strncmp(kind, "KEY_LETTER", kindLen) == 0) {
key->halves = LETTER_HALVES;
legendCopy(key->legend, a[0]);
key->plain = charOf(a[1]);
key->shifted = (uint8_t)a[0][0];
} else if (strncmp(kind, "KEY_DIGIT", kindLen) == 0) {
key->halves = LETTER_HALVES;
legendCopy(key->legend, a[0]);
key->plain = charOf(a[1]);
key->shifted = charOf(a[2]);
} else if (strncmp(kind, "KEY_PUNCT", kindLen) == 0) {
key->halves = (uint32_t)strtoul(a[0], NULL, DECIMAL);
key->legend[0] = (char)charOf(a[1]);
key->legend[1] = 0;
key->plain = charOf(a[1]);
key->shifted = charOf(a[2]);
} else if (strncmp(kind, "KEY_NAMED", kindLen) == 0) {
key->halves = (uint32_t)strtoul(a[0], NULL, DECIMAL);
legendCopy(key->legend, a[3]);
key->plain = charOf(a[2]);
key->shifted = key->plain;
} else if (strncmp(kind, "KEY_LATCH", kindLen) == 0) {
key->halves = (uint32_t)strtoul(a[0], NULL, DECIMAL);
legendCopy(key->legend, a[3]);
key->plain = NO_CHAR;
key->shifted = NO_CHAR;
} else {
return;
}
row->count++;
}
// A legend from its argument, its quotes taken off.
static void legendCopy(char *legend, const char *text) {
size_t len;
len = strlen(text);
if (len >= 2u && text[0] == '"' && text[len - 1u] == '"') {
text++;
len -= 2u;
}
if (len >= LEGEND_MAX) {
len = LEGEND_MAX - 1u;
}
memcpy(legend, text, len);
legend[len] = 0;
}
// a modulo n, never negative.
static int mod(int a, int n) {
return ((a % n) + n) % n;
}
// The keyboard's rows, and the cursor where it starts, from its source.
static void parse(BoardT *board, const char *text) {
char a[ARGS_MAX][ARG_MAX];
const char *at;
const char *body;
const char *bodyEnd;
const char *line;
const char *lineEnd;
const char *kind;
const char *open;
const char *close;
size_t kindLen;
memset(board, 0, sizeof(*board));
board->row = defineOf(text, ROW_START);
board->col = defineOf(text, COL_START);
for (at = strstr(text, ROW_OPEN); at != NULL; at = strstr(bodyEnd, ROW_OPEN)) {
body = strstr(at, ROW_OPEN_TAIL);
bodyEnd = body == NULL ? NULL : strstr(body, ROW_CLOSE);
if (bodyEnd == NULL || board->count == ROWS_MAX) {
toolFail("cannot read the keyboard's rows");
}
for (line = body + strlen(ROW_OPEN_TAIL); line < bodyEnd; line = lineEnd + 1) {
lineEnd = memchr(line, '\n', (size_t)(bodyEnd - line));
lineEnd = lineEnd == NULL ? bodyEnd : lineEnd;
kind = strstr(line, "KEY_");
if (kind == NULL || kind >= lineEnd) {
continue;
}
for (kindLen = 4; kind[kindLen] >= 'A' && kind[kindLen] <= 'Z'; kindLen++) {
}
open = kind + kindLen;
close = lineEnd;
while (close > open && (close[-1] == ' ' || close[-1] == '\t' || close[-1] == '\r')) {
close--;
}
close -= close > open && close[-1] == ',' ? 1 : 0;
if (*open != '(' || close <= open + 1 || close[-1] != ')') {
continue;
}
memset(a, 0, sizeof(a));
argsSplit(open + 1, (size_t)(close - 1 - (open + 1)), a);
keyAdd(&board->rows[board->count], kind, kindLen, a);
}
board->count++;
}
}
// The moves from the cursor to a key, as the keyboard makes them: rows
// first, then along the row; the cursor (board->row, board->col) is left
// on it.
static void path(BoardT *board, uint32_t toRow, uint32_t toCol) {
uint32_t row;
uint32_t col;
uint32_t middle;
uint32_t best;
uint32_t distance;
uint32_t c;
int n;
int right;
int left;
bool down;
row = board->row;
col = board->col;
n = (int)board->count;
while (row != toRow) {
middle = centre(&board->rows[row], col);
down = mod((int)toRow - (int)row, n) <= mod((int)row - (int)toRow, n);
row = (uint32_t)mod((int)row + (down ? 1 : -1), n);
best = UINT32_MAX;
for (c = 0; c < board->rows[row].count; c++) {
distance = centre(&board->rows[row], c);
distance = distance > middle ? distance - middle : middle - distance;
if (distance < best || (down && distance == best)) {
best = distance;
col = c;
}
}
puts(down ? "move down" : "move up");
}
n = (int)board->rows[row].count;
right = mod((int)toCol - (int)col, n);
left = mod((int)col - (int)toCol, n);
for (; right <= left && right > 0; right--) {
puts("move right");
}
for (; right > left && left > 0; left--) {
puts("move left");
}
board->row = row;
board->col = toCol;
}
// The moves to the key with legend want (or that types it, Shift first for
// a shifted character) and its press.
static void press(BoardT *board, const char *want, bool byLegend) {
uint32_t row;
uint32_t col;
uint32_t shiftRow;
uint32_t shiftCol;
bool shifted;
bool unused;
find(board, want, byLegend, &row, &col, &shifted);
if (shifted) {
find(board, SHIFT_LEGEND, true, &shiftRow, &shiftCol, &unused);
path(board, shiftRow, shiftCol);
printf("press %s\n", SHIFT_LEGEND);
}
path(board, row, col);
printf("press %s\n", board->rows[row].keys[col].legend);
}
int main(int argc, char **argv) {
BoardT board;
uint8_t *file;
size_t len;
char *text;
const char *value;
char one[2];
int i;
bool byLegend;
toolFileName(TOOL_NAME);
if (argc < 3) {
fprintf(stderr, "usage: %s SOURCE TOKEN... (TOKEN: key:LEGEND or type:TEXT)\n", TOOL_NAME);
return 1;
}
if (!toolReadFile(argv[1], &file, &len)) {
toolFail("cannot read %s", argv[1]);
}
text = (char *)realloc(file, len + 1u);
if (text == NULL) {
toolFail("out of memory");
}
text[len] = 0;
parse(&board, text);
for (i = 2; i < argc; i++) {
value = strchr(argv[i], ':');
value = value == NULL ? argv[i] + strlen(argv[i]) : value + 1;
byLegend = strncmp(argv[i], "key:", strlen("key:")) == 0;
if (byLegend) {
press(&board, value, true);
continue;
}
for (one[1] = 0; *value != 0; value++) {
one[0] = *value;
press(&board, one, false);
}
}
return 0;
}