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