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:
+268
-15
@@ -96,6 +96,53 @@ uint8_t colour_gray = 0b1000;
|
||||
|
||||
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;
|
||||
|
||||
bool newTiles = true;
|
||||
@@ -172,35 +219,242 @@ int end() {
|
||||
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() {
|
||||
usb_HandleEvents();
|
||||
if(has_srl_device) {
|
||||
char in_buf[1024];
|
||||
/* Read up to 1024 bytes from the serial buffer */
|
||||
size_t bytes_read = srl_Read(&srl, in_buf, sizeof in_buf);
|
||||
idx = 0;
|
||||
char symbol;
|
||||
char escape_code[16];
|
||||
bool bs = false;
|
||||
/* Check for an error (e.g. device disconneced) */
|
||||
if(bytes_read < 0) {
|
||||
dbg_printf("error %d on srl_Read\n", bytes_read);
|
||||
has_srl_device = false;
|
||||
} else if(bytes_read > 0) {
|
||||
while (idx < bytes_read) {
|
||||
symbol = in_buf[idx];
|
||||
if (bytes_read > 0) {
|
||||
for (idx = 0; idx < bytes_read; idx++) {
|
||||
char symbol = in_buf[idx];
|
||||
|
||||
/* --- Ctrl+V literal-next: bypass all parsing for exactly one byte --- */
|
||||
if (literalNext) {
|
||||
literalNext = false;
|
||||
if (cursorX >= 40) {
|
||||
cursorX = 0;
|
||||
cursorY += 1;
|
||||
}
|
||||
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;
|
||||
if (symbol == 0x27) {};
|
||||
bool bs = false;
|
||||
bool wasPrevCR = sawCR;
|
||||
sawCR = false;
|
||||
if (symbol == 0x7F) {
|
||||
symbol = 0x20;
|
||||
cursorX -= (cursorX>0);
|
||||
increment_X = 0;
|
||||
}
|
||||
if (symbol == 0x0D) {
|
||||
if (symbol == 0x0D) { /* CR - also treated as a full newline on its own */
|
||||
cursorX = 0;
|
||||
cursorY += 1;
|
||||
symbol = 0x20;
|
||||
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) {
|
||||
srl_Write(&srl, "TerminalCE", 10);
|
||||
@@ -221,11 +475,10 @@ bool step() {
|
||||
if (cursorY > 25) {
|
||||
scroll();
|
||||
}
|
||||
renderGoal[(40 * (cursorY+2)) + cursorX] = ((symbol << 8)|0xF0);
|
||||
renderGoal[(40 * (cursorY+2)) + cursorX] = (uint16_t)((symbol << 8) | colourByte());
|
||||
if (!bs) {
|
||||
cursorX += increment_X;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
newTiles = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user