1#include "vterm_internal.h"
2
3#include <stdio.h>
4#include <string.h>
5
6#define CSI_ARGS_MAX 16
7#define CSI_LEADER_MAX 16
8#define CSI_INTERMED_MAX 16
9
10static void do_control(VTerm *vt, unsigned char control)
11{
12  if(vt->parser_callbacks && vt->parser_callbacks->control)
13    if((*vt->parser_callbacks->control)(control, vt->cbdata))
14      return;
15
16  fprintf(stderr, "libvterm: Unhandled control 0x%02x\n", control);
17}
18
19static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command)
20{
21  size_t i = 0;
22
23  int leaderlen = 0;
24  char leader[CSI_LEADER_MAX];
25
26  int argcount = 1; // Always at least 1 arg
27  long csi_args[CSI_ARGS_MAX];
28  int argi;
29
30  int intermedlen = 0;
31  char intermed[CSI_INTERMED_MAX];
32
33  // Extract leader bytes 0x3c to 0x3f
34  for( ; i < arglen; i++) {
35    if(args[i] < 0x3c || args[i] > 0x3f)
36      break;
37    if(leaderlen < CSI_LEADER_MAX-1)
38      leader[leaderlen++] = args[i];
39  }
40
41  leader[leaderlen] = 0;
42
43  for( ; i < arglen; i++)
44    if(args[i] == 0x3b || args[i] == 0x3a) // ; or :
45      argcount++;
46
47  /* TODO: Consider if these buffers should live in the VTerm struct itself */
48  if(argcount > CSI_ARGS_MAX)
49    argcount = CSI_ARGS_MAX;
50
51  for(argi = 0; argi < argcount; argi++)
52    csi_args[argi] = CSI_ARG_MISSING;
53
54  argi = 0;
55  for(i = leaderlen; i < arglen && argi < argcount; i++) {
56    switch(args[i]) {
57    case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
58    case 0x35: case 0x36: case 0x37: case 0x38: case 0x39:
59      if(csi_args[argi] == CSI_ARG_MISSING)
60        csi_args[argi] = 0;
61      csi_args[argi] *= 10;
62      csi_args[argi] += args[i] - '0';
63      break;
64    case 0x3a:
65      csi_args[argi] |= CSI_ARG_FLAG_MORE;
66      /* FALLTHROUGH */
67    case 0x3b:
68      argi++;
69      break;
70    default:
71      goto done_leader;
72    }
73  }
74done_leader: ;
75
76  for( ; i < arglen; i++) {
77    if((args[i] & 0xf0) != 0x20)
78      break;
79
80    if(intermedlen < CSI_INTERMED_MAX-1)
81      intermed[intermedlen++] = args[i];
82  }
83
84  intermed[intermedlen] = 0;
85
86  if(i < arglen) {
87    fprintf(stderr, "libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i);
88  }
89
90  //printf("Parsed CSI args %.*s as:\n", arglen, args);
91  //printf(" leader: %s\n", leader);
92  //for(argi = 0; argi < argcount; argi++) {
93  //  printf(" %lu", CSI_ARG(csi_args[argi]));
94  //  if(!CSI_ARG_HAS_MORE(csi_args[argi]))
95  //    printf("\n");
96  //printf(" intermed: %s\n", intermed);
97  //}
98
99  if(vt->parser_callbacks && vt->parser_callbacks->csi)
100    if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata))
101      return;
102
103  fprintf(stderr, "libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command);
104}
105
106static void append_strbuffer(VTerm *vt, const char *str, size_t len)
107{
108  if(len > vt->strbuffer_len - vt->strbuffer_cur) {
109    len = vt->strbuffer_len - vt->strbuffer_cur;
110    fprintf(stderr, "Truncating strbuffer preserve to %zd bytes\n", len);
111  }
112
113  if(len > 0) {
114    strncpy(vt->strbuffer + vt->strbuffer_cur, str, len);
115    vt->strbuffer_cur += len;
116  }
117}
118
119static size_t do_string(VTerm *vt, const char *str_frag, size_t len)
120{
121  size_t eaten;
122
123  if(vt->strbuffer_cur) {
124    if(str_frag)
125      append_strbuffer(vt, str_frag, len);
126
127    str_frag = vt->strbuffer;
128    len = vt->strbuffer_cur;
129  }
130  else if(!str_frag) {
131    fprintf(stderr, "parser.c: TODO: No strbuffer _and_ no final fragment???\n");
132    len = 0;
133  }
134
135  vt->strbuffer_cur = 0;
136
137  switch(vt->parser_state) {
138  case NORMAL:
139    if(vt->parser_callbacks && vt->parser_callbacks->text)
140      if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata)))
141        return eaten;
142
143    fprintf(stderr, "libvterm: Unhandled text (%zu chars)\n", len);
144    return 0;
145
146  case ESC:
147    if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) {
148      // C1 emulations using 7bit clean
149      // ESC 0x40 == 0x80
150      do_control(vt, str_frag[0] + 0x40);
151      return 0;
152    }
153
154    if(vt->parser_callbacks && vt->parser_callbacks->escape)
155      if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata))
156        return 0;
157
158    fprintf(stderr, "libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]);
159    return 0;
160
161  case CSI:
162    do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]);
163    return 0;
164
165  case OSC:
166    if(vt->parser_callbacks && vt->parser_callbacks->osc)
167      if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata))
168        return 0;
169
170    fprintf(stderr, "libvterm: Unhandled OSC %.*s\n", (int)len, str_frag);
171    return 0;
172
173  case DCS:
174    if(vt->parser_callbacks && vt->parser_callbacks->dcs)
175      if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata))
176        return 0;
177
178    fprintf(stderr, "libvterm: Unhandled DCS %.*s\n", (int)len, str_frag);
179    return 0;
180
181  case ESC_IN_OSC:
182  case ESC_IN_DCS:
183    fprintf(stderr, "libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n");
184    return 0;
185  }
186
187  return 0;
188}
189
190void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len)
191{
192  size_t pos = 0;
193  const char *string_start = NULL;
194
195  switch(vt->parser_state) {
196  case NORMAL:
197    string_start = NULL;
198    break;
199  case ESC:
200  case ESC_IN_OSC:
201  case ESC_IN_DCS:
202  case CSI:
203  case OSC:
204  case DCS:
205    string_start = bytes;
206    break;
207  }
208
209#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0)
210#define ENTER_NORMAL_STATE()   do { vt->parser_state = NORMAL; string_start = NULL; } while(0)
211
212  for( ; pos < len; pos++) {
213    unsigned char c = bytes[pos];
214
215    if(c == 0x00 || c == 0x7f) { // NUL, DEL
216      if(vt->parser_state != NORMAL) {
217        append_strbuffer(vt, string_start, bytes + pos - string_start);
218        string_start = bytes + pos + 1;
219      }
220      continue;
221    }
222    if(c == 0x18 || c == 0x1a) { // CAN, SUB
223      ENTER_NORMAL_STATE();
224      continue;
225    }
226    else if(c == 0x1b) { // ESC
227      if(vt->parser_state == OSC)
228        vt->parser_state = ESC_IN_OSC;
229      else if(vt->parser_state == DCS)
230        vt->parser_state = ESC_IN_DCS;
231      else
232        ENTER_STRING_STATE(ESC);
233      continue;
234    }
235    else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
236            (vt->parser_state == OSC || vt->parser_state == DCS)) {
237      // fallthrough
238    }
239    else if(c < 0x20) { // other C0
240      if(vt->parser_state != NORMAL)
241        append_strbuffer(vt, string_start, bytes + pos - string_start);
242      do_control(vt, c);
243      if(vt->parser_state != NORMAL)
244        string_start = bytes + pos + 1;
245      continue;
246    }
247    // else fallthrough
248
249    switch(vt->parser_state) {
250    case ESC_IN_OSC:
251    case ESC_IN_DCS:
252      if(c == 0x5c) { // ST
253        switch(vt->parser_state) {
254          case ESC_IN_OSC: vt->parser_state = OSC; break;
255          case ESC_IN_DCS: vt->parser_state = DCS; break;
256          default: break;
257        }
258        do_string(vt, string_start, bytes + pos - string_start - 1);
259        ENTER_NORMAL_STATE();
260        break;
261      }
262      vt->parser_state = ESC;
263      string_start = bytes + pos;
264      // else fallthrough
265
266    case ESC:
267      switch(c) {
268      case 0x50: // DCS
269        ENTER_STRING_STATE(DCS);
270        break;
271      case 0x5b: // CSI
272        ENTER_STRING_STATE(CSI);
273        break;
274      case 0x5d: // OSC
275        ENTER_STRING_STATE(OSC);
276        break;
277      default:
278        if(c >= 0x30 && c < 0x7f) {
279          /* +1 to pos because we want to include this command byte as well */
280          do_string(vt, string_start, bytes + pos - string_start + 1);
281          ENTER_NORMAL_STATE();
282        }
283        else if(c >= 0x20 && c < 0x30) {
284          /* intermediate byte */
285        }
286        else {
287          fprintf(stderr, "TODO: Unhandled byte %02x in Escape\n", c);
288        }
289      }
290      break;
291
292    case CSI:
293      if(c >= 0x40 && c <= 0x7f) {
294        /* +1 to pos because we want to include this command byte as well */
295        do_string(vt, string_start, bytes + pos - string_start + 1);
296        ENTER_NORMAL_STATE();
297      }
298      break;
299
300    case OSC:
301    case DCS:
302      if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
303        do_string(vt, string_start, bytes + pos - string_start);
304        ENTER_NORMAL_STATE();
305      }
306      break;
307
308    case NORMAL:
309      if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
310        switch(c) {
311        case 0x90: // DCS
312          ENTER_STRING_STATE(DCS);
313          break;
314        case 0x9b: // CSI
315          ENTER_STRING_STATE(CSI);
316          break;
317        case 0x9d: // OSC
318          ENTER_STRING_STATE(OSC);
319          break;
320        default:
321          do_control(vt, c);
322          break;
323        }
324      }
325      else {
326        size_t text_eaten = do_string(vt, bytes + pos, len - pos);
327
328        if(text_eaten == 0) {
329          string_start = bytes + pos;
330          goto pause;
331        }
332
333        pos += (text_eaten - 1); // we'll ++ it again in a moment
334      }
335      break;
336    }
337  }
338
339pause:
340  if(string_start && string_start < len + bytes) {
341    size_t remaining = len - (string_start - bytes);
342    append_strbuffer(vt, string_start, remaining);
343  }
344}
345