xref: /vim-8.2.3635/src/libvterm/src/parser.c (revision 591cec83)
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 int 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_LOG1("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.v.csi.leader);
27   for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) {
28     printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi]));
29     if(!CSI_ARG_HAS_MORE(vt->parser.v.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.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL,
38           vt->parser.v.csi.args,
39           vt->parser.v.csi.argi,
40           vt->parser.intermedlen ? vt->parser.intermed : NULL,
41           command,
42           vt->parser.cbdata))
43       return;
44 
45   DEBUG_LOG1("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_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", command);
62 }
63 
string_fragment(VTerm * vt,const char * str,size_t len,int final)64 static void string_fragment(VTerm *vt, const char *str, size_t len, int final)
65 {
66   VTermStringFragment frag;
67 
68   frag.str = str;
69   frag.len = len;
70   frag.initial = vt->parser.string_initial;
71   frag.final = final;
72 
73   switch(vt->parser.state) {
74     case OSC:
75       if(vt->parser.callbacks && vt->parser.callbacks->osc)
76         (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);
77       break;
78 
79     case DCS:
80       if(len && vt->parser.callbacks && vt->parser.callbacks->dcs)
81         (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);
82       break;
83 
84     case NORMAL:
85     case CSI_LEADER:
86     case CSI_ARGS:
87     case CSI_INTERMED:
88     case OSC_COMMAND:
89     case DCS_COMMAND:
90       break;
91   }
92 
93   vt->parser.string_initial = FALSE;
94 }
95 
vterm_input_write(VTerm * vt,const char * bytes,size_t len)96 size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
97 {
98   size_t pos = 0;
99   const char *string_start = NULL;  // init to avoid gcc warning
100 
101   vt->in_backspace = 0;		    // Count down with BS key and activate when
102 				    // it reaches 1
103 
104   switch(vt->parser.state) {
105   case NORMAL:
106   case CSI_LEADER:
107   case CSI_ARGS:
108   case CSI_INTERMED:
109   case OSC_COMMAND:
110   case DCS_COMMAND:
111     string_start = NULL;
112     break;
113   case OSC:
114   case DCS:
115     string_start = bytes;
116     break;
117   }
118 
119 #define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)
120 #define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)
121 
122 #define IS_STRING_STATE()      (vt->parser.state >= OSC_COMMAND)
123 
124   for( ; pos < len; pos++) {
125     unsigned char c = bytes[pos];
126     int c1_allowed = !vt->mode.utf8;
127     size_t string_len;
128 
129     if(c == 0x00 || c == 0x7f) { // NUL, DEL
130       if(IS_STRING_STATE()) {
131         string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
132         string_start = bytes + pos + 1;
133       }
134       continue;
135     }
136     if(c == 0x18 || c == 0x1a) { // CAN, SUB
137       vt->parser.in_esc = FALSE;
138       ENTER_NORMAL_STATE();
139       continue;
140     }
141     else if(c == 0x1b) { // ESC
142       vt->parser.intermedlen = 0;
143       if(!IS_STRING_STATE())
144         vt->parser.state = NORMAL;
145       vt->parser.in_esc = TRUE;
146       continue;
147     }
148     else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
149             IS_STRING_STATE()) {
150       // fallthrough
151     }
152     else if(c < 0x20) { // other C0
153       if(vterm_get_special_pty_type() == 2) {
154         if(c == 0x08) // BS
155           // Set the trick for BS output after a sequence, to delay backspace
156           // activation
157           if(pos + 2 < len && bytes[pos + 1] == 0x20 && bytes[pos + 2] == 0x08)
158             vt->in_backspace = 2; // Trigger when count down to 1
159       }
160       if(IS_STRING_STATE())
161         string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
162       do_control(vt, c);
163       if(IS_STRING_STATE())
164         string_start = bytes + pos + 1;
165       continue;
166     }
167     // else fallthrough
168 
169     string_len = bytes + pos - string_start;
170 
171     if(vt->parser.in_esc) {
172       // Hoist an ESC letter into a C1 if we're not in a string mode
173       // Always accept ESC \ == ST even in string mode
174       if(!vt->parser.intermedlen &&
175           c >= 0x40 && c < 0x60 &&
176           ((!IS_STRING_STATE() || c == 0x5c))) {
177         c += 0x40;
178         c1_allowed = TRUE;
179         string_len -= 1;
180         vt->parser.in_esc = FALSE;
181       }
182       else {
183         string_start = NULL;
184         vt->parser.state = NORMAL;
185       }
186     }
187 
188     switch(vt->parser.state) {
189     case CSI_LEADER:
190       /* Extract leader bytes 0x3c to 0x3f */
191       if(c >= 0x3c && c <= 0x3f) {
192         if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1)
193           vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c;
194         break;
195       }
196 
197       /* else fallthrough */
198       vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;
199 
200       vt->parser.v.csi.argi = 0;
201       vt->parser.v.csi.args[0] = CSI_ARG_MISSING;
202       vt->parser.state = CSI_ARGS;
203 
204       /* fallthrough */
205     case CSI_ARGS:
206       /* Numerical value of argument */
207       if(c >= '0' && c <= '9') {
208         if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING)
209           vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0;
210         vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10;
211         vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0';
212         break;
213       }
214       if(c == ':') {
215         vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;
216         c = ';';
217       }
218       if(c == ';') {
219         vt->parser.v.csi.argi++;
220         vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;
221         break;
222       }
223 
224       /* else fallthrough */
225       vt->parser.v.csi.argi++;
226       vt->parser.intermedlen = 0;
227       vt->parser.state = CSI_INTERMED;
228       // fallthrough
229     case CSI_INTERMED:
230       if(is_intermed(c)) {
231         if(vt->parser.intermedlen < INTERMED_MAX-1)
232           vt->parser.intermed[vt->parser.intermedlen++] = c;
233         break;
234       }
235       else if(c == 0x1b) {
236         /* ESC in CSI cancels */
237       }
238       else if(c >= 0x40 && c <= 0x7e) {
239         vt->parser.intermed[vt->parser.intermedlen] = 0;
240         do_csi(vt, c);
241       }
242       /* else was invalid CSI */
243 
244       ENTER_NORMAL_STATE();
245       break;
246 
247     case OSC_COMMAND:
248       /* Numerical value of command */
249       if(c >= '0' && c <= '9') {
250         if(vt->parser.v.osc.command == -1)
251           vt->parser.v.osc.command = 0;
252         else
253           vt->parser.v.osc.command *= 10;
254         vt->parser.v.osc.command += c - '0';
255         break;
256       }
257       if(c == ';') {
258         vt->parser.state = OSC;
259         string_start = bytes + pos + 1;
260         break;
261       }
262 
263       /* else fallthrough */
264       string_start = bytes + pos;
265       string_len   = 0;
266       vt->parser.state = OSC;
267       goto string_state;
268 
269     case DCS_COMMAND:
270       if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX)
271         vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c;
272 
273       if(c >= 0x40 && c<= 0x7e) {
274         string_start = bytes + pos + 1;
275         vt->parser.state = DCS;
276       }
277       break;
278 
279 string_state:
280     case OSC:
281     case DCS:
282       if(c == 0x07 || (c1_allowed && c == 0x9c)) {
283         string_fragment(vt, string_start, string_len, TRUE);
284         ENTER_NORMAL_STATE();
285       }
286       break;
287 
288     case NORMAL:
289       if(vt->parser.in_esc) {
290         if(is_intermed(c)) {
291           if(vt->parser.intermedlen < INTERMED_MAX-1)
292             vt->parser.intermed[vt->parser.intermedlen++] = c;
293         }
294         else if(c >= 0x30 && c < 0x7f) {
295           do_escape(vt, c);
296           vt->parser.in_esc = 0;
297           ENTER_NORMAL_STATE();
298         }
299         else {
300           DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
301         }
302         break;
303       }
304       if(c1_allowed && c >= 0x80 && c < 0xa0) {
305         switch(c) {
306         case 0x90: // DCS
307           vt->parser.string_initial = TRUE;
308           vt->parser.v.dcs.commandlen = 0;
309           ENTER_STATE(DCS_COMMAND);
310           break;
311         case 0x9b: // CSI
312           vt->parser.v.csi.leaderlen = 0;
313           ENTER_STATE(CSI_LEADER);
314           break;
315         case 0x9d: // OSC
316           vt->parser.v.osc.command = -1;
317           vt->parser.string_initial = TRUE;
318           string_start = bytes + pos + 1;
319           ENTER_STATE(OSC_COMMAND);
320           break;
321         default:
322           do_control(vt, c);
323           break;
324         }
325       }
326       else {
327         size_t eaten = 0;
328         if(vt->parser.callbacks && vt->parser.callbacks->text)
329           eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);
330 
331         if(!eaten) {
332           DEBUG_LOG("libvterm: Text callback did not consume any input\n");
333           /* force it to make progress */
334           eaten = 1;
335         }
336 
337         pos += (eaten - 1); // we'll ++ it again in a moment
338       }
339       break;
340     }
341   }
342 
343   if(string_start)
344     string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
345 
346   return len;
347 }
348 
vterm_parser_set_callbacks(VTerm * vt,const VTermParserCallbacks * callbacks,void * user)349 void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
350 {
351   vt->parser.callbacks = callbacks;
352   vt->parser.cbdata = user;
353 }
354 
vterm_parser_get_cbdata(VTerm * vt)355 void *vterm_parser_get_cbdata(VTerm *vt)
356 {
357   return vt->parser.cbdata;
358 }
359