diff options
Diffstat (limited to 'src/parser.c')
-rw-r--r-- | src/parser.c | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..13bbc21 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,344 @@ +#include "vterm_internal.h" + +#include <stdio.h> +#include <string.h> + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 +#define CSI_INTERMED_MAX 16 + +static void do_control(VTerm *vt, unsigned char control) +{ + if(vt->parser_callbacks && vt->parser_callbacks->control) + if((*vt->parser_callbacks->control)(control, vt->cbdata)) + return; + + fprintf(stderr, "libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command) +{ + int i = 0; + + int leaderlen = 0; + char leader[CSI_LEADER_MAX]; + + // Extract leader bytes 0x3c to 0x3f + for( ; i < arglen; i++) { + if(args[i] < 0x3c || args[i] > 0x3f) + break; + if(leaderlen < CSI_LEADER_MAX-1) + leader[leaderlen++] = args[i]; + } + + leader[leaderlen] = 0; + + int argcount = 1; // Always at least 1 arg + + for( ; i < arglen; i++) + if(args[i] == 0x3b || args[i] == 0x3a) // ; or : + argcount++; + + /* TODO: Consider if these buffers should live in the VTerm struct itself */ + long csi_args[CSI_ARGS_MAX]; + if(argcount > CSI_ARGS_MAX) + argcount = CSI_ARGS_MAX; + + int argi; + for(argi = 0; argi < argcount; argi++) + csi_args[argi] = CSI_ARG_MISSING; + + argi = 0; + for(i = leaderlen; i < arglen && argi < argcount; i++) { + switch(args[i]) { + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: + case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: + if(csi_args[argi] == CSI_ARG_MISSING) + csi_args[argi] = 0; + csi_args[argi] *= 10; + csi_args[argi] += args[i] - '0'; + break; + case 0x3a: + csi_args[argi] |= CSI_ARG_FLAG_MORE; + /* FALLTHROUGH */ + case 0x3b: + argi++; + break; + default: + goto done_leader; + } + } +done_leader: ; + + int intermedlen = 0; + char intermed[CSI_INTERMED_MAX]; + + for( ; i < arglen; i++) { + if((args[i] & 0xf0) != 0x20) + break; + + if(intermedlen < CSI_INTERMED_MAX-1) + intermed[intermedlen++] = args[i]; + } + + intermed[intermedlen] = 0; + + if(i < arglen) { + fprintf(stderr, "libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i); + } + + //printf("Parsed CSI args %.*s as:\n", arglen, args); + //printf(" leader: %s\n", leader); + //for(argi = 0; argi < argcount; argi++) { + // printf(" %lu", CSI_ARG(csi_args[argi])); + // if(!CSI_ARG_HAS_MORE(csi_args[argi])) + // printf("\n"); + //printf(" intermed: %s\n", intermed); + //} + + if(vt->parser_callbacks && vt->parser_callbacks->csi) + if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata)) + return; + + fprintf(stderr, "libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command); +} + +static void append_strbuffer(VTerm *vt, const char *str, size_t len) +{ + if(len > vt->strbuffer_len - vt->strbuffer_cur) { + len = vt->strbuffer_len - vt->strbuffer_cur; + fprintf(stderr, "Truncating strbuffer preserve to %zd bytes\n", len); + } + + if(len > 0) { + strncpy(vt->strbuffer + vt->strbuffer_cur, str, len); + vt->strbuffer_cur += len; + } +} + +static size_t do_string(VTerm *vt, const char *str_frag, size_t len) +{ + if(vt->strbuffer_cur) { + if(str_frag) + append_strbuffer(vt, str_frag, len); + + str_frag = vt->strbuffer; + len = vt->strbuffer_cur; + } + else if(!str_frag) { + fprintf(stderr, "parser.c: TODO: No strbuffer _and_ no final fragment???\n"); + len = 0; + } + + vt->strbuffer_cur = 0; + + size_t eaten; + + switch(vt->parser_state) { + case NORMAL: + if(vt->parser_callbacks && vt->parser_callbacks->text) + if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata))) + return eaten; + + fprintf(stderr, "libvterm: Unhandled text (%zu chars)\n", len); + return 0; + + case ESC: + if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) { + // C1 emulations using 7bit clean + // ESC 0x40 == 0x80 + do_control(vt, str_frag[0] + 0x40); + return 0; + } + + if(vt->parser_callbacks && vt->parser_callbacks->escape) + if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata)) + return 0; + + fprintf(stderr, "libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]); + return 0; + + case CSI: + do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]); + return 0; + + case OSC: + if(vt->parser_callbacks && vt->parser_callbacks->osc) + if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata)) + return 0; + + fprintf(stderr, "libvterm: Unhandled OSC %.*s\n", (int)len, str_frag); + return 0; + + case DCS: + if(vt->parser_callbacks && vt->parser_callbacks->dcs) + if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata)) + return 0; + + fprintf(stderr, "libvterm: Unhandled DCS %.*s\n", (int)len, str_frag); + return 0; + + case ESC_IN_OSC: + case ESC_IN_DCS: + fprintf(stderr, "libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n"); + return 0; + } + + return 0; +} + +void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) +{ + size_t pos = 0; + const char *string_start; + + switch(vt->parser_state) { + case NORMAL: + string_start = NULL; + break; + case ESC: + case ESC_IN_OSC: + case ESC_IN_DCS: + case CSI: + case OSC: + case DCS: + string_start = bytes; + break; + } + +#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0) +#define ENTER_NORMAL_STATE() do { vt->parser_state = NORMAL; string_start = NULL; } while(0) + + for( ; pos < len; pos++) { + unsigned char c = bytes[pos]; + + if(c == 0x00 || c == 0x7f) { // NUL, DEL + if(vt->parser_state != NORMAL) { + append_strbuffer(vt, string_start, bytes + pos - string_start); + string_start = bytes + pos + 1; + } + continue; + } + if(c == 0x18 || c == 0x1a) { // CAN, SUB + ENTER_NORMAL_STATE(); + continue; + } + else if(c == 0x1b) { // ESC + if(vt->parser_state == OSC) + vt->parser_state = ESC_IN_OSC; + else if(vt->parser_state == DCS) + vt->parser_state = ESC_IN_DCS; + else + ENTER_STRING_STATE(ESC); + continue; + } + else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state + (vt->parser_state == OSC || vt->parser_state == DCS)) { + // fallthrough + } + else if(c < 0x20) { // other C0 + if(vt->parser_state != NORMAL) + append_strbuffer(vt, string_start, bytes + pos - string_start); + do_control(vt, c); + if(vt->parser_state != NORMAL) + string_start = bytes + pos + 1; + continue; + } + // else fallthrough + + switch(vt->parser_state) { + case ESC_IN_OSC: + case ESC_IN_DCS: + if(c == 0x5c) { // ST + switch(vt->parser_state) { + case ESC_IN_OSC: vt->parser_state = OSC; break; + case ESC_IN_DCS: vt->parser_state = DCS; break; + default: break; + } + do_string(vt, string_start, bytes + pos - string_start - 1); + ENTER_NORMAL_STATE(); + break; + } + vt->parser_state = ESC; + string_start = bytes + pos; + // else fallthrough + + case ESC: + switch(c) { + case 0x50: // DCS + ENTER_STRING_STATE(DCS); + break; + case 0x5b: // CSI + ENTER_STRING_STATE(CSI); + break; + case 0x5d: // OSC + ENTER_STRING_STATE(OSC); + break; + default: + if(c >= 0x30 && c < 0x7f) { + /* +1 to pos because we want to include this command byte as well */ + do_string(vt, string_start, bytes + pos - string_start + 1); + ENTER_NORMAL_STATE(); + } + else if(c >= 0x20 && c < 0x30) { + /* intermediate byte */ + } + else { + fprintf(stderr, "TODO: Unhandled byte %02x in Escape\n", c); + } + } + break; + + case CSI: + if(c >= 0x40 && c <= 0x7f) { + /* +1 to pos because we want to include this command byte as well */ + do_string(vt, string_start, bytes + pos - string_start + 1); + ENTER_NORMAL_STATE(); + } + break; + + case OSC: + case DCS: + if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { + do_string(vt, string_start, bytes + pos - string_start); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { + switch(c) { + case 0x90: // DCS + ENTER_STRING_STATE(DCS); + break; + case 0x9b: // CSI + ENTER_STRING_STATE(CSI); + break; + case 0x9d: // OSC + ENTER_STRING_STATE(OSC); + break; + default: + do_control(vt, c); + break; + } + } + else { + size_t text_eaten = do_string(vt, bytes + pos, len - pos); + + if(text_eaten == 0) { + string_start = bytes + pos; + goto pause; + } + + pos += (text_eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + +pause: + if(string_start && string_start < len + bytes) { + size_t remaining = len - (string_start - bytes); + append_strbuffer(vt, string_start, remaining); + } +} |