diff options
Diffstat (limited to 'src/parser.c')
-rw-r--r-- | src/parser.c | 414 |
1 files changed, 205 insertions, 209 deletions
diff --git a/src/parser.c b/src/parser.c index 13bbc21..a01cd71 100644 --- a/src/parser.c +++ b/src/parser.c @@ -3,218 +3,154 @@ #include <stdio.h> #include <string.h> -#define CSI_ARGS_MAX 16 -#define CSI_LEADER_MAX 16 -#define CSI_INTERMED_MAX 16 +#undef DEBUG_PARSER + +static bool is_intermed(unsigned char c) +{ + return c >= 0x20 && c <= 0x2f; +} 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)) + if(vt->parser.callbacks && vt->parser.callbacks->control) + if((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) return; - fprintf(stderr, "libvterm: Unhandled control 0x%02x\n", control); + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); } -static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command) +static void do_csi(VTerm *vt, 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; - } +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.csi_leader); + for(int argi = 0; argi < vt->parser.csi_argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.csi_args[argi])); + if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi])) + printf("\n"); + printf(" intermed: %s\n", vt->parser.intermed); } -done_leader: ; - - int intermedlen = 0; - char intermed[CSI_INTERMED_MAX]; - - for( ; i < arglen; i++) { - if((args[i] & 0xf0) != 0x20) - break; +#endif + + if(vt->parser.callbacks && vt->parser.callbacks->csi) + if((*vt->parser.callbacks->csi)( + vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL, + vt->parser.csi_args, + vt->parser.csi_argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) + return; - if(intermedlen < CSI_INTERMED_MAX-1) - intermed[intermedlen++] = args[i]; - } + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} - intermed[intermedlen] = 0; +static void do_escape(VTerm *vt, char command) +{ + char seq[INTERMED_MAX+1]; - if(i < arglen) { - fprintf(stderr, "libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i); - } + size_t len = vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); + seq[len++] = command; + seq[len] = 0; - //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)) + if(vt->parser.callbacks && vt->parser.callbacks->escape) + if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) return; - fprintf(stderr, "libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command); + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", 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 > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) { + len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur; + DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len); } if(len > 0) { - strncpy(vt->strbuffer + vt->strbuffer_cur, str, len); - vt->strbuffer_cur += len; + strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len); + vt->parser.strbuffer_cur += len; } } -static size_t do_string(VTerm *vt, const char *str_frag, size_t len) +static void start_string(VTerm *vt, VTermParserStringType type) { - 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; + vt->parser.stringtype = type; - 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; - } + vt->parser.strbuffer_cur = 0; +} - if(vt->parser_callbacks && vt->parser_callbacks->escape) - if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata)) - return 0; +static void more_string(VTerm *vt, const char *str, size_t len) +{ + append_strbuffer(vt, str, len); +} - fprintf(stderr, "libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]); - return 0; +static void done_string(VTerm *vt, const char *str, size_t len) +{ + if(vt->parser.strbuffer_cur) { + if(str) + append_strbuffer(vt, str, len); - case CSI: - do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]); - return 0; + str = vt->parser.strbuffer; + len = vt->parser.strbuffer_cur; + } + else if(!str) { + DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n"); + len = 0; + } - case OSC: - if(vt->parser_callbacks && vt->parser_callbacks->osc) - if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata)) - return 0; + switch(vt->parser.stringtype) { + case VTERM_PARSER_OSC: + if(vt->parser.callbacks && vt->parser.callbacks->osc) + if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata)) + return; - fprintf(stderr, "libvterm: Unhandled OSC %.*s\n", (int)len, str_frag); - return 0; + DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str); + return; - case DCS: - if(vt->parser_callbacks && vt->parser_callbacks->dcs) - if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata)) - return 0; + case VTERM_PARSER_DCS: + if(vt->parser.callbacks && vt->parser.callbacks->dcs) + if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata)) + return; - fprintf(stderr, "libvterm: Unhandled DCS %.*s\n", (int)len, str_frag); - return 0; + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str); + return; - case ESC_IN_OSC: - case ESC_IN_DCS: - fprintf(stderr, "libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n"); - return 0; + case VTERM_N_PARSER_TYPES: + return; } - - return 0; } -void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) { size_t pos = 0; const char *string_start; - switch(vt->parser_state) { + switch(vt->parser.state) { case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case ESC: string_start = NULL; break; - case ESC: - case ESC_IN_OSC: - case ESC_IN_DCS: - case CSI: - case OSC: - case DCS: + case STRING: + case ESC_IN_STRING: 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) +#define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0) +#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) 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); + if(vt->parser.state >= STRING) { + more_string(vt, string_start, bytes + pos - string_start); string_start = bytes + pos + 1; } continue; @@ -224,83 +160,131 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) 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; + vt->parser.intermedlen = 0; + if(vt->parser.state == STRING) + vt->parser.state = ESC_IN_STRING; else - ENTER_STRING_STATE(ESC); + ENTER_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)) { + vt->parser.state == STRING) { // fallthrough } else if(c < 0x20) { // other C0 - if(vt->parser_state != NORMAL) - append_strbuffer(vt, string_start, bytes + pos - string_start); + if(vt->parser.state >= STRING) + more_string(vt, string_start, bytes + pos - string_start); do_control(vt, c); - if(vt->parser_state != NORMAL) + if(vt->parser.state >= STRING) string_start = bytes + pos + 1; continue; } // else fallthrough - switch(vt->parser_state) { - case ESC_IN_OSC: - case ESC_IN_DCS: + switch(vt->parser.state) { + case ESC_IN_STRING: 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); + vt->parser.state = STRING; + done_string(vt, string_start, bytes + pos - string_start - 1); ENTER_NORMAL_STATE(); break; } - vt->parser_state = ESC; - string_start = bytes + pos; + vt->parser.state = ESC; // else fallthrough case ESC: switch(c) { case 0x50: // DCS - ENTER_STRING_STATE(DCS); + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); break; case 0x5b: // CSI - ENTER_STRING_STATE(CSI); + vt->parser.csi_leaderlen = 0; + ENTER_STATE(CSI_LEADER); break; case 0x5d: // OSC - ENTER_STRING_STATE(OSC); + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); 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); + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } + else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) { + do_control(vt, c + 0x40); ENTER_NORMAL_STATE(); } - else if(c >= 0x20 && c < 0x30) { - /* intermediate byte */ + else if(c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + ENTER_NORMAL_STATE(); } else { - fprintf(stderr, "TODO: Unhandled byte %02x in Escape\n", c); + DEBUG_LOG("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(); + case CSI_LEADER: + /* Extract leader bytes 0x3c to 0x3f */ + if(c >= 0x3c && c <= 0x3f) { + if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1) + vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c; + break; + } + + /* else fallthrough */ + vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; + + vt->parser.csi_argi = 0; + vt->parser.csi_args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + /* fallthrough */ + case CSI_ARGS: + /* Numerical value of argument */ + if(c >= '0' && c <= '9') { + if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING) + vt->parser.csi_args[vt->parser.csi_argi] = 0; + vt->parser.csi_args[vt->parser.csi_argi] *= 10; + vt->parser.csi_args[vt->parser.csi_argi] += c - '0'; + break; + } + if(c == ':') { + vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; + c = ';'; } + if(c == ';') { + vt->parser.csi_argi++; + vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; + break; + } + + /* else fallthrough */ + vt->parser.csi_argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + case CSI_INTERMED: + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + break; + } + else if(c == 0x1b) { + /* ESC in CSI cancels */ + } + else if(c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, c); + } + /* else was invalid CSI */ + + ENTER_NORMAL_STATE(); break; - case OSC: - case DCS: + case STRING: if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { - do_string(vt, string_start, bytes + pos - string_start); + done_string(vt, string_start, bytes + pos - string_start); ENTER_NORMAL_STATE(); } break; @@ -309,13 +293,15 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { switch(c) { case 0x90: // DCS - ENTER_STRING_STATE(DCS); + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); break; case 0x9b: // CSI - ENTER_STRING_STATE(CSI); + ENTER_STATE(CSI_LEADER); break; case 0x9d: // OSC - ENTER_STRING_STATE(OSC); + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); break; default: do_control(vt, c); @@ -323,22 +309,32 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) } } else { - size_t text_eaten = do_string(vt, bytes + pos, len - pos); - - if(text_eaten == 0) { - string_start = bytes + pos; - goto pause; + size_t eaten = 0; + if(vt->parser.callbacks && vt->parser.callbacks->text) + eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + + if(!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + /* force it to make progress */ + eaten = 1; } - pos += (text_eaten - 1); // we'll ++ it again in a moment + pos += (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); - } + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} + +void *vterm_parser_get_cbdata(VTerm *vt) +{ + return vt->parser.cbdata; } |