MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <string.h>
2#include <stdbool.h>
3
4#include "tokens.h"
5#include "highlight.h"
6#include "highlight/regex.h"
7#include "silver/lexer.h"
8
9typedef struct {
10 const char *op;
11 int len;
12 hl_token_class cls;
13} op_entry_t;
14
15static const op_entry_t operators[] = {
16 { "===", 3, HL_OPERATOR },
17 { "!==", 3, HL_OPERATOR },
18 { "...", 3, HL_OPERATOR },
19 { "=>", 2, HL_OPERATOR },
20 { "==", 2, HL_OPERATOR },
21 { "!=", 2, HL_OPERATOR },
22 { "<=", 2, HL_OPERATOR },
23 { ">=", 2, HL_OPERATOR },
24 { "&&", 2, HL_OPERATOR },
25 { "||", 2, HL_OPERATOR },
26 { "??", 2, HL_OPERATOR },
27 { "?.", 2, HL_OPTIONAL_CHAIN },
28};
29
30#define OP_COUNT (sizeof(operators) / sizeof(operators[0]))
31#define K(s, t) if (len == sizeof(s)-1 && !memcmp(word, s, sizeof(s)-1)) return t
32
33static hl_token_class lookup_extra_keyword(const char *word, size_t len) {
34 switch (word[0]) {
35 case 'a':
36 K("abstract", HL_TYPE);
37 K("async", HL_KEYWORD_ITALIC);
38 break;
39 case 'b': K("boolean", HL_TYPE_BOOLEAN); break;
40 case 'd': K("declare", HL_TYPE); break;
41 case 'e':
42 K("enum", HL_TYPE);
43 K("export", HL_KEYWORD_ITALIC);
44 break;
45 case 'g': K("global", HL_KEYWORD_ITALIC); break;
46 case 'i':
47 K("interface", HL_TYPE);
48 K("implements", HL_TYPE);
49 break;
50 case 'n':
51 K("namespace", HL_TYPE);
52 K("never", HL_TYPE);
53 break;
54 case 'o': K("object", HL_TYPE); break;
55 case 'p':
56 K("package", HL_KEYWORD);
57 K("private", HL_KEYWORD);
58 K("protected", HL_KEYWORD);
59 K("public", HL_KEYWORD);
60 break;
61 case 'r': K("readonly", HL_TYPE); break;
62 case 's':
63 K("string", HL_TYPE_STRING);
64 K("symbol", HL_TYPE_STRING);
65 break;
66 case 't': K("type", HL_TYPE); break;
67 case 'u': K("unknown", HL_TYPE); break;
68 } return HL_NONE;
69}
70
71#undef K
72
73static hl_token_class tok_to_class(uint8_t tok) {
74 static const void *dispatch[] = {
75 [TOK_ASYNC] = &&l_kw_italic,
76 [TOK_EXPORT] = &&l_kw_italic,
77 [TOK_THIS] = &&l_kw_italic,
78 [TOK_GLOBAL_THIS] = &&l_kw_italic,
79 [TOK_WINDOW] = &&l_kw_italic,
80 [TOK_DELETE] = &&l_kw_delete,
81 [TOK_TYPEOF] = &&l_type,
82 [TOK_INSTANCEOF] = &&l_type,
83 [TOK_OF] = &&l_type,
84 [TOK_IN] = &&l_type,
85 [TOK_AS] = &&l_type,
86 [TOK_TRUE] = &&l_bool,
87 [TOK_FALSE] = &&l_bool,
88 [TOK_NULL] = &&l_null,
89 [TOK_UNDEF] = &&l_null,
90 };
91
92 if (tok <= TOK_IDENTIFIER || tok >= TOK_IDENT_LIKE_END) return HL_NONE;
93 if (tok < sizeof(dispatch) / sizeof(*dispatch) && dispatch[tok]) goto *dispatch[tok];
94
95 return HL_KEYWORD;
96
97 l_kw_italic: return HL_KEYWORD_ITALIC;
98 l_kw_delete: return HL_KEYWORD_DELETE;
99 l_type: return HL_TYPE;
100 l_bool: return HL_BOOLEAN;
101 l_null: return HL_LITERAL_NULL;
102}
103
104void hl_iter_init(hl_iter *it, const char *input, size_t input_len, const highlight_state *state) {
105 it->input = input;
106 it->input_len = input_len;
107 it->pos = 0;
108 it->state = state ? *state : HL_STATE_INIT;
109 it->ctx = HL_CTX_NONE;
110}
111
112static hl_context keyword_sets_context(const char *word, size_t len) {
113 if (len == 8 && memcmp(word, "function", 8) == 0) return HL_CTX_AFTER_FUNCTION;
114 if (len == 5 && memcmp(word, "class", 5) == 0) return HL_CTX_AFTER_CLASS;
115 if (len == 7 && memcmp(word, "extends", 7) == 0) return HL_CTX_AFTER_EXTENDS;
116 return HL_CTX_NONE;
117}
118
119static size_t skip_inline_ws_forward(const char *input, size_t input_len, size_t i) {
120 while (i < input_len && (input[i] == ' ' || input[i] == '\t' || input[i] == '\n' || input[i] == '\r')) i++;
121 return i;
122}
123
124static size_t skip_inline_ws_backward(const char *input, size_t i) {
125 while (i > 0 && (input[i - 1] == ' ' || input[i - 1] == '\t' || input[i - 1] == '\n' || input[i - 1] == '\r')) i--;
126 return i;
127}
128
129static bool read_prev_word(const char *input, size_t end, size_t *word_start, size_t *word_len) {
130 size_t i = skip_inline_ws_backward(input, end);
131 if (i == 0 || !is_ident_continue((unsigned char)input[i - 1])) return false;
132
133 size_t wend = i;
134 while (i > 0 && is_ident_continue((unsigned char)input[i - 1])) i--;
135
136 *word_start = i;
137 *word_len = wend - i;
138 return true;
139}
140
141static bool is_arrow_after(const char *input, size_t input_len, size_t pos) {
142 size_t i = skip_inline_ws_forward(input, input_len, pos);
143 return (i + 1 < input_len && input[i] == '=' && input[i + 1] == '>');
144}
145
146static bool find_matching_close_paren(const char *input, size_t input_len, size_t open_paren, size_t *close_paren) {
147 size_t depth = 0;
148 for (size_t i = open_paren + 1; i < input_len; i++) {
149 unsigned char ch = (unsigned char)input[i];
150 if (ch == '(') {
151 depth++;
152 continue;
153 }
154 if (ch == ')') {
155 if (depth == 0) {
156 *close_paren = i;
157 return true;
158 }
159 depth--;
160 }
161 }
162 return false;
163}
164
165static bool has_declaration_keyword_before(const char *input, size_t ident_start) {
166 size_t ws, wstart, wlen;
167 ws = skip_inline_ws_backward(input, ident_start);
168 if (ws == 0) return false;
169
170 size_t wend = ws;
171 size_t i = ws;
172 while (i > 0 && is_ident_continue((unsigned char)input[i - 1])) i--;
173 wstart = i;
174 wlen = wend - i;
175
176 return
177 (wlen == 5 && memcmp(input + wstart, "const", 5) == 0) ||
178 (wlen == 3 && memcmp(input + wstart, "let", 3) == 0) ||
179 (wlen == 5 && memcmp(input + wstart, "using", 5) == 0) ||
180 (wlen == 3 && memcmp(input + wstart, "var", 3) == 0);
181}
182
183static bool is_assigned_arrow_function(const char *input, size_t input_len, size_t ident_start, size_t ident_end) {
184 if (!has_declaration_keyword_before(input, ident_start)) return false;
185 size_t i = skip_inline_ws_forward(input, input_len, ident_end);
186
187 if (i >= input_len || input[i] != '=') return false;
188 if (i + 1 < input_len && input[i + 1] == '>') return false;
189 i++; i = skip_inline_ws_forward(input, input_len, i);
190
191 if (i >= input_len) return false;
192 if (is_arrow_after(input, input_len, i)) return false;
193
194 if (is_ident_begin((unsigned char)input[i])) {
195 size_t j = i + 1;
196 while (j < input_len && is_ident_continue((unsigned char)input[j])) j++;
197 if (is_arrow_after(input, input_len, j)) return true;
198 }
199
200 if (input[i] == '(') {
201 size_t close = 0;
202 if (find_matching_close_paren(input, input_len, i, &close))
203 if (is_arrow_after(input, input_len, close + 1)) return true;
204 }
205
206 if (is_ident_begin((unsigned char)input[i]) || input[i] == '(') {
207 size_t j = i;
208 if (input[j] == '(') {
209 size_t close = 0;
210 if (find_matching_close_paren(input, input_len, j, &close)) j = close + 1;
211 else return false;
212 } else {
213 j++;
214 while (j < input_len && is_ident_continue((unsigned char)input[j])) j++;
215 }
216
217 j = skip_inline_ws_forward(input, input_len, j);
218 if (j < input_len && input[j] == ':') {
219 j++;
220 size_t depth = 0;
221 while (j < input_len) {
222 unsigned char ch = (unsigned char)input[j];
223 if (ch == '(' || ch == '<') { depth++; j++; continue; }
224 if (ch == ')' || ch == '>') {
225 if (depth > 0) depth--;
226 j++;
227 continue;
228 }
229 if (depth == 0 && (ch == '=' || ch == ',')) break;
230 j++;
231 }
232 if (is_arrow_after(input, input_len, j)) return true;
233 }
234 }
235
236 if (input[i] == 'a') {
237 if (i + 5 <= input_len && memcmp(input + i, "async", 5) == 0 &&
238 (i + 5 >= input_len || !is_ident_continue((unsigned char)input[i + 5]))) {
239 size_t j = skip_inline_ws_forward(input, input_len, i + 5);
240 if (j < input_len && input[j] == '(') {
241 size_t close = 0;
242 if (find_matching_close_paren(input, input_len, j, &close))
243 if (is_arrow_after(input, input_len, close + 1)) return true;
244 }
245 if (j < input_len && is_ident_begin((unsigned char)input[j])) {
246 size_t k = j + 1;
247 while (k < input_len && is_ident_continue((unsigned char)input[k])) k++;
248 if (is_arrow_after(input, input_len, k)) return true;
249 }
250 }
251 }
252
253 if (input[i] == 'f') {
254 if (i + 8 <= input_len && memcmp(input + i, "function", 8) == 0 &&
255 (i + 8 >= input_len || !is_ident_continue((unsigned char)input[i + 8])))
256 return true;
257 }
258
259 return false;
260}
261
262static bool has_function_keyword_before_paren(const char *input, size_t open_paren) {
263 size_t word_start = 0;
264 size_t word_len = 0;
265
266 if (!read_prev_word(input, open_paren, &word_start, &word_len)) return false;
267 if (word_len == 8 && memcmp(input + word_start, "function", 8) == 0) return true;
268
269 if (!read_prev_word(input, word_start, &word_start, &word_len)) return false;
270 return (word_len == 8 && memcmp(input + word_start, "function", 8) == 0);
271}
272
273static bool is_control_paren_prefix(const char *input, size_t open_paren) {
274 size_t word_start = 0;
275 size_t word_len = 0;
276 if (!read_prev_word(input, open_paren, &word_start, &word_len)) return false;
277
278#define C(s) (word_len == sizeof(s) - 1 && memcmp(input + word_start, s, sizeof(s) - 1) == 0)
279 return C("if") || C("for") || C("while") || C("switch") || C("catch") || C("with");
280#undef C
281}
282
283static bool is_likely_function_param_paren(
284 const char *input, size_t input_len,
285 size_t open_paren, size_t close_paren
286) {
287 if (is_arrow_after(input, input_len, close_paren + 1)) return true;
288 if (has_function_keyword_before_paren(input, open_paren)) return true;
289
290 size_t after = skip_inline_ws_forward(input, input_len, close_paren + 1);
291 if (after < input_len && input[after] == '{' && !is_control_paren_prefix(input, open_paren))
292 return true;
293
294 return false;
295}
296
297static bool find_enclosing_open_paren(const char *input, size_t pos, size_t *open_paren) {
298 size_t depth = 0;
299 size_t i = pos;
300
301 while (i > 0) {
302 i--;
303 unsigned char ch = (unsigned char)input[i];
304 if (ch == ')') {
305 depth++;
306 continue;
307 }
308 if (ch == '(') {
309 if (depth == 0) {
310 *open_paren = i;
311 return true;
312 }
313 depth--;
314 }
315 }
316 return false;
317}
318
319static bool is_function_argument_identifier(const char *input, size_t input_len, size_t start, size_t end) {
320 if (is_arrow_after(input, input_len, end)) {
321 size_t left = skip_inline_ws_backward(input, start);
322 if (left > 0 && input[left - 1] == '.') return false;
323 return true;
324 }
325
326 size_t prev = skip_inline_ws_backward(input, start);
327 if (prev == 0) return false;
328 unsigned char prev_ch = (unsigned char)input[prev - 1];
329 if (!(prev_ch == '(' || prev_ch == ',' || prev_ch == '{' || prev_ch == '[' || prev_ch == ':'))
330 return false;
331
332 size_t open_paren = 0;
333 if (!find_enclosing_open_paren(input, start, &open_paren)) return false;
334
335 size_t close_paren = 0;
336 if (!find_matching_close_paren(input, input_len, open_paren, &close_paren)) return false;
337 return is_likely_function_param_paren(input, input_len, open_paren, close_paren);
338}
339
340static inline bool is_line_comment_terminator(unsigned char c) {
341 return c == '\n' || c == '\r';
342}
343
344bool hl_iter_next(hl_iter *it, hl_span *out) {
345 const char *input = it->input;
346 size_t input_len = it->input_len;
347 size_t i = it->pos;
348
349 if (i >= input_len) return false;
350 unsigned char c = (unsigned char)input[i];
351
352 if (it->state.mode == HL_STATE_BLOCK_COMMENT) {
353 size_t start = i;
354 while (i < input_len) {
355 if (input[i] == '*' && i + 1 < input_len && input[i + 1] == '/') {
356 i += 2;
357 it->state.mode = HL_STATE_NORMAL;
358 break;
359 }
360 i++;
361 }
362 *out = (hl_span){ start, i - start, HL_COMMENT };
363 it->pos = i;
364 return true;
365 }
366
367 if (i == 0 && c == '#' && i + 1 < input_len && input[i + 1] == '!') {
368 while (i < input_len && input[i] != '\n') i++;
369 *out = (hl_span){ 0, i, HL_COMMENT };
370 it->pos = i;
371 return true;
372 }
373
374 if (it->state.mode == HL_STATE_STRING_SINGLE || it->state.mode == HL_STATE_STRING_DOUBLE) {
375 char quote = (it->state.mode == HL_STATE_STRING_SINGLE) ? '\'' : '"';
376 size_t start = i;
377 while (i < input_len) {
378 if (input[i] == '\\' && i + 1 < input_len) { i += 2; continue; }
379 if (input[i] == quote) {
380 i++;
381 it->state.mode = (it->state.template_depth > 0) ? HL_STATE_TEMPLATE_EXPR : HL_STATE_NORMAL;
382 break;
383 }
384 i++;
385 }
386 *out = (hl_span){ start, i - start, HL_STRING };
387 it->pos = i;
388 return true;
389 }
390
391 if (it->state.mode == HL_STATE_TEMPLATE) {
392 size_t start = i;
393 while (i < input_len) {
394 if (input[i] == '\\' && i + 1 < input_len) { i += 2; continue; }
395 if (input[i] == '$' && i + 1 < input_len && input[i + 1] == '{') {
396 i += 2;
397 it->state.mode = HL_STATE_TEMPLATE_EXPR;
398 it->state.template_depth++;
399 break;
400 }
401 if (input[i] == '`') {
402 i++;
403 it->state.mode = (it->state.template_depth > 0) ? HL_STATE_TEMPLATE_EXPR : HL_STATE_NORMAL;
404 break;
405 }
406 i++;
407 }
408 *out = (hl_span){ start, i - start, HL_STRING };
409 it->pos = i;
410 return true;
411 }
412
413 if (it->state.mode == HL_STATE_TEMPLATE_EXPR && c == '}') {
414 it->state.template_depth--;
415 if (it->state.template_depth <= 0) {
416 it->state.mode = HL_STATE_TEMPLATE;
417 it->state.template_depth = 0;
418 *out = (hl_span){ i, 1, HL_BRACKET };
419 it->pos = i + 1;
420 return true;
421 }
422 }
423 if (it->state.mode == HL_STATE_TEMPLATE_EXPR && c == '{') {
424 it->state.template_depth++;
425 *out = (hl_span){ i, 1, HL_BRACKET };
426 it->pos = i + 1;
427 return true;
428 }
429
430 if (c == '/' && i + 1 < input_len && input[i + 1] == '/') {
431 it->ctx = HL_CTX_NONE;
432 size_t start = i; i += 2;
433 while (i < input_len && !is_line_comment_terminator((unsigned char)input[i])) i++;
434 *out = (hl_span){ start, i - start, HL_COMMENT };
435 it->pos = i;
436 return true;
437 }
438
439 if (c == '/' && i + 1 < input_len && input[i + 1] == '*') {
440 it->ctx = HL_CTX_NONE;
441 size_t start = i;
442 i += 2;
443 while (i + 1 < input_len && !(input[i] == '*' && input[i + 1] == '/')) i++;
444 if (i + 1 < input_len) {
445 i += 2;
446 } else {
447 i = input_len;
448 it->state.mode = HL_STATE_BLOCK_COMMENT;
449 }
450 *out = (hl_span){ start, i - start, HL_COMMENT };
451 it->pos = i;
452 return true;
453 }
454
455 if (c == '/') {
456 size_t regex_end = 0;
457 if (js_scan_regex_literal(input, input_len, i, ®ex_end)) {
458 it->ctx = HL_CTX_NONE;
459 *out = (hl_span){ i, regex_end - i, HL_REGEX };
460 it->pos = regex_end;
461 return true;
462 }
463 }
464
465 if (c == '\'' || c == '"') {
466 it->ctx = HL_CTX_NONE;
467 size_t start = i;
468 it->state.mode = (c == '\'') ? HL_STATE_STRING_SINGLE : HL_STATE_STRING_DOUBLE;
469 i++;
470 while (i < input_len) {
471 if (input[i] == '\\' && i + 1 < input_len) { i += 2; continue; }
472 if ((unsigned char)input[i] == c) {
473 i++;
474 it->state.mode = (it->state.template_depth > 0) ? HL_STATE_TEMPLATE_EXPR : HL_STATE_NORMAL;
475 break;
476 }
477 i++;
478 }
479 *out = (hl_span){ start, i - start, HL_STRING };
480 it->pos = i;
481 return true;
482 }
483
484 if (c == '`') {
485 it->ctx = HL_CTX_NONE;
486 it->state.mode = HL_STATE_TEMPLATE;
487 *out = (hl_span){ i, 1, HL_STRING };
488 it->pos = i + 1;
489 return true;
490 }
491
492 if (c == ';') {
493 it->ctx = HL_CTX_NONE;
494 *out = (hl_span){ i, 1, HL_SEMICOLON };
495 it->pos = i + 1;
496 return true;
497 }
498
499 if (IS_DIGIT(c) || (c == '.' && i + 1 < input_len && IS_DIGIT(input[i + 1]))) {
500 it->ctx = HL_CTX_NONE;
501 size_t start = i;
502 if (c == '0' && i + 1 < input_len) {
503 unsigned char next = (unsigned char)input[i + 1];
504 if (next == 'x' || next == 'X') {
505 i += 2;
506 while (i < input_len && (IS_XDIGIT(input[i]) || input[i] == '_')) i++;
507 goto num_done;
508 } else if (next == 'b' || next == 'B') {
509 i += 2;
510 while (i < input_len && (input[i] == '0' || input[i] == '1' || input[i] == '_')) i++;
511 goto num_done;
512 } else if (next == 'o' || next == 'O') {
513 i += 2;
514 while (i < input_len && (IS_OCTAL(input[i]) || input[i] == '_')) i++;
515 goto num_done;
516 }
517 }
518 while (i < input_len && (IS_DIGIT(input[i]) || input[i] == '_')) i++;
519 if (i < input_len && input[i] == '.') {
520 i++;
521 while (i < input_len && (IS_DIGIT(input[i]) || input[i] == '_')) i++;
522 }
523 if (i < input_len && (input[i] == 'e' || input[i] == 'E')) {
524 i++;
525 if (i < input_len && (input[i] == '+' || input[i] == '-')) i++;
526 while (i < input_len && (IS_DIGIT(input[i]) || input[i] == '_')) i++;
527 }
528 num_done:
529 if (i < input_len && input[i] == 'n') i++;
530 *out = (hl_span){ start, i - start, HL_NUMBER };
531 it->pos = i;
532 return true;
533 }
534
535 for (int k = 0; k < (int)OP_COUNT; k++) {
536 int oplen = operators[k].len;
537 if (i + (size_t)oplen <= input_len &&
538 memcmp(input + i, operators[k].op, (size_t)oplen) == 0) {
539 it->ctx = HL_CTX_NONE;
540 *out = (hl_span){ i, (size_t)oplen, operators[k].cls };
541 it->pos = i + (size_t)oplen;
542 return true;
543 }
544 }
545
546 if (c == '#' && i + 1 < input_len && is_ident_begin((unsigned char)input[i + 1])) {
547 size_t start = i;
548 i += 2;
549 while (i < input_len && is_ident_continue((unsigned char)input[i])) i++;
550 it->ctx = HL_CTX_NONE;
551 *out = (hl_span){ start, i - start, HL_PROPERTY };
552 it->pos = i;
553 return true;
554 }
555
556 if (is_ident_begin(c)) {
557 size_t start = i;
558 i++;
559 while (i < input_len && is_ident_continue(input[i])) i++;
560 size_t word_len = i - start;
561 const char *word = input + start;
562
563 bool is_member_access = (start > 0 && input[start - 1] == '.' &&
564 (start < 2 || input[start - 2] != '.'));
565 bool is_method = false;
566 if (is_member_access) {
567 size_t peek = i;
568 while (peek < input_len && input[peek] == ' ') peek++;
569 if (peek < input_len && input[peek] == '(') is_method = true;
570 }
571 size_t after_word = i;
572 while (after_word < input_len && input[after_word] == ' ') after_word++;
573 bool is_call = (after_word < input_len && input[after_word] == '(');
574
575 hl_token_class cls = HL_NONE;
576 bool is_console = (word_len == 7 && memcmp(word, "console", 7) == 0);
577
578 if (is_console) {
579 cls = HL_PROPERTY;
580 } else if (is_function_argument_identifier(input, input_len, start, i)) {
581 cls = HL_ARGUMENT;
582 } else if (is_method) {
583 cls = HL_FUNCTION;
584 } else if (is_member_access) {
585 cls = HL_PROPERTY;
586 } else if (is_assigned_arrow_function(input, input_len, start, i)) {
587 cls = HL_FUNCTION_NAME;
588 } else if (it->ctx == HL_CTX_AFTER_FUNCTION) {
589 cls = HL_FUNCTION_NAME;
590 it->ctx = HL_CTX_NONE;
591 } else if (it->ctx == HL_CTX_AFTER_CLASS) {
592 cls = HL_CLASS_NAME;
593 it->ctx = HL_CTX_NONE;
594 } else if (it->ctx == HL_CTX_AFTER_EXTENDS) {
595 cls = HL_PARENT_CLASS;
596 it->ctx = HL_CTX_NONE;
597 } else {
598 cls = lookup_extra_keyword(word, word_len);
599
600 if (cls == HL_NONE) {
601 if ((word_len == 3 && memcmp(word, "NaN", 3) == 0) ||
602 (word_len == 8 && memcmp(word, "Infinity", 8) == 0)) {
603 cls = HL_NUMBER;
604 }
605 else if (word_len == 7 && memcmp(word, "extends", 7) == 0) {
606 cls = HL_KEYWORD_EXTENDS;
607 } else {
608 cls = tok_to_class(sv_parsekeyword(word, word_len));
609 }
610 }
611
612 if (cls == HL_NONE) {
613 size_t peek = i;
614 while (peek < input_len && input[peek] == ' ') peek++;
615 if (peek < input_len && input[peek] == ':' &&
616 (peek + 1 >= input_len || input[peek + 1] != ':'))
617 cls = HL_PROPERTY;
618 }
619
620 if (cls == HL_NONE && word[0] >= 'A' && word[0] <= 'Z') {
621 cls = HL_TYPE;
622 }
623
624 if (cls == HL_NONE && is_call) {
625 cls = HL_FUNCTION;
626 }
627
628 hl_context next_ctx = keyword_sets_context(word, word_len);
629 if (next_ctx != HL_CTX_NONE) it->ctx = next_ctx;
630 }
631
632 *out = (hl_span){ start, word_len, cls };
633 it->pos = i;
634 return true;
635 }
636
637 if (c == '<' || c == '>' || c == '=' ||
638 c == '+' || c == '-' || c == '*' || c == '/' ||
639 c == '%' || c == '&' || c == '|' || c == '^' ||
640 c == '~' || c == '!' || c == '?') {
641 it->ctx = HL_CTX_NONE;
642 *out = (hl_span){ i, 1, HL_OPERATOR };
643 it->pos = i + 1;
644 return true;
645 }
646
647 if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
648 it->ctx = HL_CTX_NONE;
649 *out = (hl_span){ i, 1, HL_BRACKET };
650 it->pos = i + 1;
651 return true;
652 }
653
654 if (c == ' ' || c == '\t') {
655 size_t start = i;
656 while (i < input_len && (input[i] == ' ' || input[i] == '\t')) i++;
657 *out = (hl_span){ start, i - start, HL_NONE };
658 it->pos = i;
659 return true;
660 }
661
662 it->ctx = HL_CTX_NONE;
663 *out = (hl_span){ i, 1, HL_NONE };
664 it->pos = i + 1;
665 return true;
666}