Home | History | Annotate | Download | only in src
      1 #include "vterm_internal.h"
      2 
      3 #include <stdio.h>
      4 #include <string.h>
      5 
      6 #undef DEBUG_PARSER
      7 
      8 static bool is_intermed(unsigned char c)
      9 {
     10   return c >= 0x20 && c <= 0x2f;
     11 }
     12 
     13 static void do_control(VTerm *vt, unsigned char control)
     14 {
     15   if(vt->parser.callbacks && vt->parser.callbacks->control)
     16     if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
     17       return;
     18 
     19   DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control);
     20 }
     21 
     22 static void do_csi(VTerm *vt, char command)
     23 {
     24 #ifdef DEBUG_PARSER
     25   printf("Parsed CSI args as:\n", arglen, args);
     26   printf(" leader: %s\n", vt->parser.csi_leader);
     27   for(int argi = 0; argi < vt->parser.csi_argi; argi++) {
     28     printf(" %lu", CSI_ARG(vt->parser.csi_args[argi]));
     29     if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi]))
     30       printf("\n");
     31   printf(" intermed: %s\n", vt->parser.intermed);
     32   }
     33 #endif
     34 
     35   if(vt->parser.callbacks && vt->parser.callbacks->csi)
     36     if((*vt->parser.callbacks->csi)(
     37           vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL,
     38           vt->parser.csi_args,
     39           vt->parser.csi_argi,
     40           vt->parser.intermedlen ? vt->parser.intermed : NULL,
     41           command,
     42           vt->parser.cbdata))
     43       return;
     44 
     45   DEBUG_LOG("libvterm: Unhandled CSI %c\n", command);
     46 }
     47 
     48 static void do_escape(VTerm *vt, char command)
     49 {
     50   char seq[INTERMED_MAX+1];
     51 
     52   size_t len = vt->parser.intermedlen;
     53   strncpy(seq, vt->parser.intermed, len);
     54   seq[len++] = command;
     55   seq[len]   = 0;
     56 
     57   if(vt->parser.callbacks && vt->parser.callbacks->escape)
     58     if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
     59       return;
     60 
     61   DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command);
     62 }
     63 
     64 static void append_strbuffer(VTerm *vt, const char *str, size_t len)
     65 {
     66   if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) {
     67     len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur;
     68     DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len);
     69   }
     70 
     71   if(len > 0) {
     72     strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len);
     73     vt->parser.strbuffer_cur += len;
     74   }
     75 }
     76 
     77 static void start_string(VTerm *vt, VTermParserStringType type)
     78 {
     79   vt->parser.stringtype = type;
     80 
     81   vt->parser.strbuffer_cur = 0;
     82 }
     83 
     84 static void more_string(VTerm *vt, const char *str, size_t len)
     85 {
     86   append_strbuffer(vt, str, len);
     87 }
     88 
     89 static void done_string(VTerm *vt, const char *str, size_t len)
     90 {
     91   if(vt->parser.strbuffer_cur) {
     92     if(str)
     93       append_strbuffer(vt, str, len);
     94 
     95     str = vt->parser.strbuffer;
     96     len = vt->parser.strbuffer_cur;
     97   }
     98   else if(!str) {
     99     DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
    100     len = 0;
    101   }
    102 
    103   switch(vt->parser.stringtype) {
    104   case VTERM_PARSER_OSC:
    105     if(vt->parser.callbacks && vt->parser.callbacks->osc)
    106       if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata))
    107         return;
    108 
    109     DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str);
    110     return;
    111 
    112   case VTERM_PARSER_DCS:
    113     if(vt->parser.callbacks && vt->parser.callbacks->dcs)
    114       if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata))
    115         return;
    116 
    117     DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str);
    118     return;
    119 
    120   case VTERM_N_PARSER_TYPES:
    121     return;
    122   }
    123 }
    124 
    125 size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
    126 {
    127   size_t pos = 0;
    128   const char *string_start;
    129 
    130   switch(vt->parser.state) {
    131   case NORMAL:
    132   case CSI_LEADER:
    133   case CSI_ARGS:
    134   case CSI_INTERMED:
    135   case ESC:
    136     string_start = NULL;
    137     break;
    138   case STRING:
    139   case ESC_IN_STRING:
    140     string_start = bytes;
    141     break;
    142   }
    143 
    144 #define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0)
    145 #define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)
    146 #define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)
    147 
    148   for( ; pos < len; pos++) {
    149     unsigned char c = bytes[pos];
    150 
    151     if(c == 0x00 || c == 0x7f) { // NUL, DEL
    152       if(vt->parser.state >= STRING) {
    153         more_string(vt, string_start, bytes + pos - string_start);
    154         string_start = bytes + pos + 1;
    155       }
    156       continue;
    157     }
    158     if(c == 0x18 || c == 0x1a) { // CAN, SUB
    159       ENTER_NORMAL_STATE();
    160       continue;
    161     }
    162     else if(c == 0x1b) { // ESC
    163       vt->parser.intermedlen = 0;
    164       if(vt->parser.state == STRING)
    165         vt->parser.state = ESC_IN_STRING;
    166       else
    167         ENTER_STATE(ESC);
    168       continue;
    169     }
    170     else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
    171             vt->parser.state == STRING) {
    172       // fallthrough
    173     }
    174     else if(c < 0x20) { // other C0
    175       if(vt->parser.state >= STRING)
    176         more_string(vt, string_start, bytes + pos - string_start);
    177       do_control(vt, c);
    178       if(vt->parser.state >= STRING)
    179         string_start = bytes + pos + 1;
    180       continue;
    181     }
    182     // else fallthrough
    183 
    184     switch(vt->parser.state) {
    185     case ESC_IN_STRING:
    186       if(c == 0x5c) { // ST
    187         vt->parser.state = STRING;
    188         done_string(vt, string_start, bytes + pos - string_start - 1);
    189         ENTER_NORMAL_STATE();
    190         break;
    191       }
    192       vt->parser.state = ESC;
    193       // else fallthrough
    194 
    195     case ESC:
    196       switch(c) {
    197       case 0x50: // DCS
    198         start_string(vt, VTERM_PARSER_DCS);
    199         ENTER_STRING_STATE();
    200         break;
    201       case 0x5b: // CSI
    202         vt->parser.csi_leaderlen = 0;
    203         ENTER_STATE(CSI_LEADER);
    204         break;
    205       case 0x5d: // OSC
    206         start_string(vt, VTERM_PARSER_OSC);
    207         ENTER_STRING_STATE();
    208         break;
    209       default:
    210         if(is_intermed(c)) {
    211           if(vt->parser.intermedlen < INTERMED_MAX-1)
    212             vt->parser.intermed[vt->parser.intermedlen++] = c;
    213         }
    214         else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) {
    215           do_control(vt, c + 0x40);
    216           ENTER_NORMAL_STATE();
    217         }
    218         else if(c >= 0x30 && c < 0x7f) {
    219           do_escape(vt, c);
    220           ENTER_NORMAL_STATE();
    221         }
    222         else {
    223           DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c);
    224         }
    225       }
    226       break;
    227 
    228     case CSI_LEADER:
    229       /* Extract leader bytes 0x3c to 0x3f */
    230       if(c >= 0x3c && c <= 0x3f) {
    231         if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1)
    232           vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c;
    233         break;
    234       }
    235 
    236       /* else fallthrough */
    237       vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0;
    238 
    239       vt->parser.csi_argi = 0;
    240       vt->parser.csi_args[0] = CSI_ARG_MISSING;
    241       vt->parser.state = CSI_ARGS;
    242 
    243       /* fallthrough */
    244     case CSI_ARGS:
    245       /* Numerical value of argument */
    246       if(c >= '0' && c <= '9') {
    247         if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING)
    248           vt->parser.csi_args[vt->parser.csi_argi] = 0;
    249         vt->parser.csi_args[vt->parser.csi_argi] *= 10;
    250         vt->parser.csi_args[vt->parser.csi_argi] += c - '0';
    251         break;
    252       }
    253       if(c == ':') {
    254         vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE;
    255         c = ';';
    256       }
    257       if(c == ';') {
    258         vt->parser.csi_argi++;
    259         vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING;
    260         break;
    261       }
    262 
    263       /* else fallthrough */
    264       vt->parser.csi_argi++;
    265       vt->parser.intermedlen = 0;
    266       vt->parser.state = CSI_INTERMED;
    267     case CSI_INTERMED:
    268       if(is_intermed(c)) {
    269         if(vt->parser.intermedlen < INTERMED_MAX-1)
    270           vt->parser.intermed[vt->parser.intermedlen++] = c;
    271         break;
    272       }
    273       else if(c == 0x1b) {
    274         /* ESC in CSI cancels */
    275       }
    276       else if(c >= 0x40 && c <= 0x7e) {
    277         vt->parser.intermed[vt->parser.intermedlen] = 0;
    278         do_csi(vt, c);
    279       }
    280       /* else was invalid CSI */
    281 
    282       ENTER_NORMAL_STATE();
    283       break;
    284 
    285     case STRING:
    286       if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
    287         done_string(vt, string_start, bytes + pos - string_start);
    288         ENTER_NORMAL_STATE();
    289       }
    290       break;
    291 
    292     case NORMAL:
    293       if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
    294         switch(c) {
    295         case 0x90: // DCS
    296           start_string(vt, VTERM_PARSER_DCS);
    297           ENTER_STRING_STATE();
    298           break;
    299         case 0x9b: // CSI
    300           ENTER_STATE(CSI_LEADER);
    301           break;
    302         case 0x9d: // OSC
    303           start_string(vt, VTERM_PARSER_OSC);
    304           ENTER_STRING_STATE();
    305           break;
    306         default:
    307           do_control(vt, c);
    308           break;
    309         }
    310       }
    311       else {
    312         size_t eaten = 0;
    313         if(vt->parser.callbacks && vt->parser.callbacks->text)
    314           eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);
    315 
    316         if(!eaten) {
    317           DEBUG_LOG("libvterm: Text callback did not consume any input\n");
    318           /* force it to make progress */
    319           eaten = 1;
    320         }
    321 
    322         pos += (eaten - 1); // we'll ++ it again in a moment
    323       }
    324       break;
    325     }
    326   }
    327 
    328   return len;
    329 }
    330 
    331 void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
    332 {
    333   vt->parser.callbacks = callbacks;
    334   vt->parser.cbdata = user;
    335 }
    336 
    337 void *vterm_parser_get_cbdata(VTerm *vt)
    338 {
    339   return vt->parser.cbdata;
    340 }
    341