I asked claude to completely replace my ansi parser attempt since it knows how to make an ansi parser and I don't

everything hardware specific is still my own code, as well as the actual renderer
This commit is contained in:
Logan Joy-Koer
2026-06-19 00:22:56 -07:00
parent 55e85c3a38
commit 7e999db4ae
+269 -16
View File
@@ -96,6 +96,53 @@ uint8_t colour_gray = 0b1000;
uint8_t keyboardState = 0x00; uint8_t keyboardState = 0x00;
/* ---------------------------------------------------------------------
* ANSI / VT100 escape sequence parser state
*
* Supports a practical subset of CSI ("ESC [ ... letter") sequences:
* CUU/CUD/CUF/CUB - cursor up/down/forward/back (A/B/C/D)
* CUP/HVP - absolute cursor position (H/f)
* ED - erase in display, modes 0/1/2 (J)
* EL - erase in line, modes 0/1/2 (K)
* SGR - colours + bold/reverse, see below (m)
*
* SGR uses the existing 4-bit (RGBI) palette: bit0=red, bit1=green,
* bit2=blue, bit3=intensity ("gray"/bold bit). Standard ANSI codes
* 30-37/40-47 map straight onto that 3-bit RGB value, and 90-97/100-107
* add the intensity bit for the "bright" variants. 256-colour / truecolour
* sequences (38;5;n / 38;2;r;g;b) aren't supported by a 16-colour display,
* so they're parsed-but-ignored rather than crashing or printing garbage.
*
* Anything else we don't recognise (DEC private modes via "ESC[?", OSC
* strings like "ESC]0;title BEL" used for window titles, single-byte
* escapes like ESC c) is consumed and discarded rather than dumped to
* the screen as literal characters.
* --------------------------------------------------------------------- */
typedef enum {
TERM_NORMAL,
TERM_ESC,
TERM_CSI,
TERM_OSC
} term_state_t;
term_state_t termState = TERM_NORMAL;
#define MAX_CSI_PARAMS 8
int csiParams[MAX_CSI_PARAMS];
uint8_t csiParamCount = 0;
bool csiPrivate = false;
uint8_t baseFgColour = 0x0F; /* default: full white text */
uint8_t baseBgColour = 0x00; /* default: black background */
bool reverseVideo = false;
/* Tracks whether the previous byte was a CR, so a CRLF pair only advances one line */
bool sawCR = false;
/* Set by Ctrl+V (0x16): the next byte is drawn as a raw glyph, bypassing all parsing */
bool literalNext = false;
int scrollAmount = 0; int scrollAmount = 0;
bool newTiles = true; bool newTiles = true;
@@ -172,35 +219,242 @@ int end() {
return exit_Code; return exit_Code;
} }
/* Current colour byte (high nibble = fg, low nibble = bg), honouring SGR reverse video */
uint8_t colourByte(void) {
uint8_t fg = reverseVideo ? baseBgColour : baseFgColour;
uint8_t bg = reverseVideo ? baseFgColour : baseBgColour;
return (uint8_t)((fg << 4) | bg);
}
/* Blank out renderGoal tiles [startIdx, endIdx] (inclusive) using the current colour */
void eraseRange(int startIdx, int endIdx) {
uint16_t blank = (uint16_t)((0x20 << 8) | colourByte());
if (startIdx < 0) startIdx = 0;
if (endIdx > 1199) endIdx = 1199;
for (int i = startIdx; i <= endIdx; i++) {
renderGoal[i] = blank;
}
newTiles = true;
}
/* Execute a fully-parsed CSI sequence ("ESC [ params finalByte") */
void handleEscapeCommand(char finalByte) {
int p0 = csiParams[0];
int n = (p0 == 0) ? 1 : p0;
int tmp;
switch (finalByte) {
case 'A': /* CUU - cursor up */
tmp = (int)cursorY - n;
cursorY = (uint8_t)(tmp < 0 ? 0 : tmp);
break;
case 'B': /* CUD - cursor down */
tmp = (int)cursorY + n;
cursorY = (uint8_t)(tmp > 25 ? 25 : tmp);
break;
case 'C': /* CUF - cursor forward */
tmp = (int)cursorX + n;
cursorX = (uint8_t)(tmp > 39 ? 39 : tmp);
break;
case 'D': /* CUB - cursor back */
tmp = (int)cursorX - n;
cursorX = (uint8_t)(tmp < 0 ? 0 : tmp);
break;
case 'H': /* CUP */
case 'f': { /* HVP - treated the same here */
int row = csiParams[0];
int col = csiParams[1];
if (row <= 0) row = 1;
if (col <= 0) col = 1;
row--;
col--;
if (row > 25) row = 25;
if (col > 39) col = 39;
cursorY = (uint8_t)row;
cursorX = (uint8_t)col;
break;
}
case 'J': { /* ED - erase in display */
int mode = csiParams[0];
int cur = (40 * (cursorY + 2)) + cursorX;
int scrStart = 80;
int scrEnd = 1119;
if (mode == 1) {
eraseRange(scrStart, cur);
} else if (mode == 2 || mode == 3) {
eraseRange(scrStart, scrEnd);
} else {
eraseRange(cur, scrEnd);
}
break;
}
case 'K': { /* EL - erase in line */
int mode = csiParams[0];
int rowStart = 40 * (cursorY + 2);
int rowEnd = rowStart + 39;
int cur = rowStart + cursorX;
if (mode == 1) {
eraseRange(rowStart, cur);
} else if (mode == 2) {
eraseRange(rowStart, rowEnd);
} else {
eraseRange(cur, rowEnd);
}
break;
}
case 'm': { /* SGR - colours / attributes */
int count = csiParamCount + 1;
for (int i = 0; i < count; i++) {
int code = csiParams[i];
if (code == 0) {
baseFgColour = (uint8_t)(colour_red | colour_green | colour_blue | colour_gray);
baseBgColour = 0x00;
reverseVideo = false;
} else if (code == 1) {
baseFgColour |= colour_gray;
} else if (code == 2 || code == 22) {
baseFgColour &= (uint8_t)~colour_gray;
} else if (code == 7) {
reverseVideo = true;
} else if (code == 27) {
reverseVideo = false;
} else if (code >= 30 && code <= 37) {
baseFgColour = (uint8_t)((baseFgColour & colour_gray) | (code - 30));
} else if (code == 39) {
baseFgColour = (uint8_t)(colour_red | colour_green | colour_blue | colour_gray);
} else if (code >= 40 && code <= 47) {
baseBgColour = (uint8_t)((baseBgColour & colour_gray) | (code - 40));
} else if (code == 49) {
baseBgColour = 0x00;
} else if (code >= 90 && code <= 97) {
baseFgColour = (uint8_t)(colour_gray | (code - 90));
} else if (code >= 100 && code <= 107) {
baseBgColour = (uint8_t)(colour_gray | (code - 100));
}
/* anything else (italics, underline, 256-colour, etc.) is silently ignored */
}
break;
}
default:
/* Unimplemented CSI command - ignore it */
break;
}
}
bool step() { bool step() {
usb_HandleEvents(); usb_HandleEvents();
if(has_srl_device) { if(has_srl_device) {
char in_buf[1024]; char in_buf[1024];
/* Read up to 1024 bytes from the serial buffer */ /* Read up to 1024 bytes from the serial buffer */
size_t bytes_read = srl_Read(&srl, in_buf, sizeof in_buf); size_t bytes_read = srl_Read(&srl, in_buf, sizeof in_buf);
idx = 0; if (bytes_read > 0) {
char symbol; for (idx = 0; idx < bytes_read; idx++) {
char escape_code[16]; char symbol = in_buf[idx];
bool bs = false;
/* Check for an error (e.g. device disconneced) */ /* --- Ctrl+V literal-next: bypass all parsing for exactly one byte --- */
if(bytes_read < 0) { if (literalNext) {
dbg_printf("error %d on srl_Read\n", bytes_read); literalNext = false;
has_srl_device = false; if (cursorX >= 40) {
} else if(bytes_read > 0) { cursorX = 0;
while (idx < bytes_read) { cursorY += 1;
symbol = in_buf[idx]; }
if (cursorY > 25) {
scroll();
}
renderGoal[(40 * (cursorY+2)) + cursorX] = (uint16_t)((symbol << 8) | colourByte());
cursorX += 1;
continue;
}
/* --- escape sequence state machine --- */
if (termState == TERM_ESC) {
if (symbol == '[') {
termState = TERM_CSI;
csiParamCount = 0;
memset(csiParams, 0, sizeof csiParams);
csiPrivate = false;
} else if (symbol == ']') {
/* OSC string (e.g. window title) - swallow until terminator */
termState = TERM_OSC;
} else {
/* Unimplemented single-byte escape (ESC c, ESC 7, etc.) - discard */
termState = TERM_NORMAL;
}
continue;
}
if (termState == TERM_OSC) {
if (symbol == 0x07 || symbol == 0x1B) { /* BEL or ESC terminates OSC */
termState = TERM_NORMAL;
}
continue;
}
if (termState == TERM_CSI) {
if (symbol >= '0' && symbol <= '9') {
if (csiParamCount < MAX_CSI_PARAMS) {
int v = (csiParams[csiParamCount] * 10) + (symbol - '0');
csiParams[csiParamCount] = (v > 9999) ? 9999 : v;
}
} else if (symbol == ';') {
if (csiParamCount < MAX_CSI_PARAMS - 1) csiParamCount++;
} else if (symbol == '?' || symbol == '>' || symbol == '=') {
/* DEC private mode marker - parse but don't act on it */
csiPrivate = true;
} else if (symbol >= 0x40 && symbol <= 0x7E) {
if (!csiPrivate) {
handleEscapeCommand(symbol);
}
termState = TERM_NORMAL;
} else {
/* Stray byte we don't expect inside a CSI sequence - bail out */
termState = TERM_NORMAL;
}
continue;
}
if (symbol == 0x1B) { /* ESC */
termState = TERM_ESC;
continue;
}
if (symbol == 0x16) { /* Ctrl+V - next byte is drawn literally */
literalNext = true;
continue;
}
/* --- plain character / legacy control-character handling --- */
int increment_X = 1; int increment_X = 1;
if (symbol == 0x27) {}; bool bs = false;
bool wasPrevCR = sawCR;
sawCR = false;
if (symbol == 0x7F) { if (symbol == 0x7F) {
symbol = 0x20; symbol = 0x20;
cursorX -= (cursorX>0); cursorX -= (cursorX>0);
increment_X = 0; increment_X = 0;
} }
if (symbol == 0x0D) { if (symbol == 0x0D) { /* CR - also treated as a full newline on its own */
cursorX = 0; cursorX = 0;
cursorY += 1; cursorY += 1;
symbol = 0x20; symbol = 0x20;
increment_X = 0; increment_X = 0;
sawCR = true;
}
if (symbol == 0x0A) { /* LF - next line; skip the move if a CR just did it */
if (!wasPrevCR) {
cursorX = 0;
cursorY += 1;
}
symbol = 0x20;
increment_X = 0;
} }
if (symbol == 0x05) { if (symbol == 0x05) {
srl_Write(&srl, "TerminalCE", 10); srl_Write(&srl, "TerminalCE", 10);
@@ -221,11 +475,10 @@ bool step() {
if (cursorY > 25) { if (cursorY > 25) {
scroll(); scroll();
} }
renderGoal[(40 * (cursorY+2)) + cursorX] = ((symbol << 8)|0xF0); renderGoal[(40 * (cursorY+2)) + cursorX] = (uint16_t)((symbol << 8) | colourByte());
if (!bs) { if (!bs) {
cursorX += increment_X; cursorX += increment_X;
} }
idx += 1;
} }
newTiles = true; newTiles = true;
} }
@@ -350,4 +603,4 @@ int main() {
cycleCounter++; cycleCounter++;
} }
return end(); return end();
} }