1 #include "vterm_internal.h"
2 
3 #include <stdio.h>
4 #include <string.h>
5 
6 #undef DEBUG_PARSER
7 
is_intermed(unsigned char c)8 static bool is_intermed(unsigned char c)
9 {
10   return c >= 0x20 && c <= 0x2f;
11 }
12 
do_control(VTerm * vt,unsigned char control)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 
do_csi(VTerm * vt,char command)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 
do_escape(VTerm * vt,char command)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 
append_strbuffer(VTerm * vt,const char * str,size_t len)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 
start_string(VTerm * vt,VTermParserStringType type)77 static void start_string(VTerm *vt, VTermParserStringType type)
78 {
79   vt->parser.stringtype = type;
80 
81   vt->parser.strbuffer_cur = 0;
82 }
83 
more_string(VTerm * vt,const char * str,size_t len)84 static void more_string(VTerm *vt, const char *str, size_t len)
85 {
86   append_strbuffer(vt, str, len);
87 }
88 
done_string(VTerm * vt,const char * str,size_t len)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 
vterm_input_write(VTerm * vt,const char * bytes,size_t len)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 
vterm_parser_set_callbacks(VTerm * vt,const VTermParserCallbacks * callbacks,void * user)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 
vterm_parser_get_cbdata(VTerm * vt)337 void *vterm_parser_get_cbdata(VTerm *vt)
338 {
339   return vt->parser.cbdata;
340 }
341