dte test coverage


Directory: ./
File: src/syntax/state.c
Date: 2025-07-03 15:44:24
Exec Total Coverage
Lines: 349 385 90.6%
Functions: 35 37 94.6%
Branches: 151 222 68.0%

Line Branch Exec Source
1 #include <errno.h>
2 #include <stdbool.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include "state.h"
6 #include "command/args.h"
7 #include "command/error.h"
8 #include "command/run.h"
9 #include "config.h"
10 #include "editor.h"
11 #include "filetype.h"
12 #include "syntax/merge.h"
13 #include "util/bsearch.h"
14 #include "util/debug.h"
15 #include "util/hashset.h"
16 #include "util/intern.h"
17 #include "util/log.h"
18 #include "util/path.h"
19 #include "util/readfile.h"
20 #include "util/strtonum.h"
21 #include "util/xmalloc.h"
22 #include "util/xsnprintf.h"
23 #include "util/xstring.h"
24
25 3901 static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
26 {
27
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 3901 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
3901 return likely(syn->current_syntax) || error_msg(ebuf, "No syntax started");
28 }
29
30 2810 static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf)
31 {
32
1/2
✓ Branch 0 (3→4) taken 2810 times.
✗ Branch 1 (3→9) not taken.
2810 if (unlikely(!in_syntax(syn, ebuf))) {
33 return false;
34 }
35
1/4
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→8) taken 2810 times.
✗ Branch 2 (6→7) not taken.
✗ Branch 3 (6→8) not taken.
2810 return likely(syn->current_state) || error_msg(ebuf, "No state started");
36 }
37
38 1242 static bool close_state(SyntaxLoader *syn, ErrorBuffer *ebuf)
39 {
40 1242 const State *state = syn->current_state;
41
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→5) taken 1241 times.
1242 if (!state) {
42 return true;
43 }
44
45 1 syn->current_state = NULL;
46
1/2
✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
1 if (likely(state->type != STATE_INVALID)) {
47 return true;
48 }
49
50 // This error applies to the state itself rather than the last command, so
51 // it doesn't make sense to include the command name in the error message
52 1 const char *name = state->name;
53 1 return error_msg_for_cmd(ebuf, NULL, "No default action in state '%s'", name);
54 }
55
56 2714 static State *find_or_add_state(const SyntaxLoader *syn, const char *name)
57 {
58 2714 State *state = find_state(syn->current_syntax, name);
59
2/2
✓ Branch 0 (3→4) taken 924 times.
✓ Branch 1 (3→9) taken 1790 times.
2714 if (state) {
60 return state;
61 }
62
63 924 state = xcalloc1(sizeof(*state));
64 924 state->name = xstrdup(name);
65 924 state->defined = false;
66 924 state->type = STATE_INVALID;
67
68
2/2
✓ Branch 0 (6→7) taken 151 times.
✓ Branch 1 (6→8) taken 773 times.
924 if (syn->current_syntax->states.count == 0) {
69 151 syn->current_syntax->start_state = state;
70 }
71
72 924 return hashmap_insert(&syn->current_syntax->states, state->name, state);
73 }
74
75 2448 static State *reference_state (
76 const SyntaxLoader *syn,
77 ErrorBuffer *ebuf,
78 const char *name
79 ) {
80
2/2
✓ Branch 0 (2→3) taken 657 times.
✓ Branch 1 (2→4) taken 1791 times.
2448 if (streq(name, "this")) {
81 657 return syn->current_state;
82 }
83
84 1791 State *state = find_or_add_state(syn, name);
85
4/4
✓ Branch 0 (5→6) taken 4 times.
✓ Branch 1 (5→8) taken 1787 times.
✓ Branch 2 (6→7) taken 1 times.
✓ Branch 3 (6→8) taken 3 times.
1791 if (unlikely((syn->flags & SYN_LINT) && state == syn->current_state)) {
86 1 error_msg (
87 ebuf,
88 "destination '%s' can be optimized to 'this' in '%s' syntax",
89 name,
90 1 syn->current_syntax->name
91 );
92 }
93
94 return state;
95 }
96
97 272 static bool in_subsyntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
98 {
99 272 bool ss = likely(is_subsyntax(syn->current_syntax));
100
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 272 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
272 return ss || error_msg(ebuf, "Destination state 'END' only allowed in a subsyntax");
101 }
102
103 188 static Syntax *must_find_subsyntax (
104 const HashMap *syntaxes,
105 ErrorBuffer *ebuf,
106 const char *name
107 ) {
108 188 Syntax *syntax = find_any_syntax(syntaxes, name);
109
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 188 times.
188 if (unlikely(!syntax)) {
110 error_msg(ebuf, "No such syntax '%s'", name);
111 return NULL;
112 }
113
1/2
✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 188 times.
188 if (unlikely(!is_subsyntax(syntax))) {
114 error_msg(ebuf, "Syntax '%s' is not a subsyntax", name);
115 return NULL;
116 }
117 return syntax;
118 }
119
120 175 static bool subsyntax_call (
121 EditorState *e,
122 const char *name,
123 const char *ret,
124 State **dest
125 ) {
126 175 Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, name);
127
128 175 SyntaxMerge m = {
129 .subsyn = subsyn,
130 .return_state = NULL,
131 .delim = NULL,
132 .delim_len = 0,
133 };
134
135
2/2
✓ Branch 0 (3→4) taken 17 times.
✓ Branch 1 (3→6) taken 158 times.
175 if (streq(ret, "END")) {
136
1/2
✓ Branch 0 (5→9) taken 17 times.
✗ Branch 1 (5→12) not taken.
17 if (!in_subsyntax(&e->syn, &e->err)) {
137 return false;
138 }
139
1/2
✓ Branch 0 (6→7) taken 158 times.
✗ Branch 1 (6→12) not taken.
158 } else if (subsyn) {
140 158 m.return_state = reference_state(&e->syn, &e->err, ret);
141 }
142
143
1/2
✓ Branch 0 (9→10) taken 175 times.
✗ Branch 1 (9→12) not taken.
175 if (subsyn) {
144 175 *dest = merge_syntax(e->syn.current_syntax, &m, &e->styles);
145 175 return true;
146 }
147
148 return false;
149 }
150
151 2720 static bool destination_state(EditorState *e, const char *name, State **dest)
152 {
153 2720 const char *sep = strchr(name, ':');
154
2/2
✓ Branch 0 (2→3) taken 175 times.
✓ Branch 1 (2→6) taken 2545 times.
2720 if (sep) {
155 // subsyntax:returnstate
156 175 char *sub = xstrcut(name, sep - name);
157 175 bool success = subsyntax_call(e, sub, sep + 1, dest);
158 175 free(sub);
159 175 return success;
160 }
161
162
2/2
✓ Branch 0 (6→7) taken 255 times.
✓ Branch 1 (6→10) taken 2290 times.
2545 if (streq(name, "END")) {
163
1/2
✓ Branch 0 (8→9) taken 255 times.
✗ Branch 1 (8→12) not taken.
255 if (!in_subsyntax(&e->syn, &e->err)) {
164 return false;
165 }
166 255 *dest = NULL;
167 255 return true;
168 }
169
170 2290 *dest = reference_state(&e->syn, &e->err, name);
171 2290 return true;
172 }
173
174 2150 static void lint_emit_name (
175 ErrorBuffer *ebuf,
176 SyntaxLoadFlags flags,
177 const char *ename,
178 const State *dest
179 ) {
180 2150 if (
181
2/2
✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→7) taken 2136 times.
2150 (flags & SYN_LINT)
182 14 && ename
183
2/2
✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→7) taken 13 times.
14 && dest
184
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→7) not taken.
1 && dest->emit_name
185
1/2
✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→7) not taken.
1 && interned_strings_equal(ename, dest->emit_name)
186 ) {
187 1 error_msg (
188 ebuf,
189 "emit-name '%s' not needed (destination state uses same emit-name)",
190 ename
191 );
192 }
193 2150 }
194
195 1888 static Condition *add_condition (
196 EditorState *e,
197 ConditionType type,
198 const char *dest,
199 const char *emit
200 ) {
201 1888 BUG_ON(!dest && cond_type_has_destination(type));
202
1/2
✓ Branch 0 (6→7) taken 1888 times.
✗ Branch 1 (6→18) not taken.
1888 if (!in_state(&e->syn, &e->err)) {
203 return NULL;
204 }
205
206 1888 State *d = NULL;
207
3/4
✓ Branch 0 (7→8) taken 1798 times.
✓ Branch 1 (7→10) taken 90 times.
✓ Branch 2 (9→10) taken 1798 times.
✗ Branch 3 (9→18) not taken.
1888 if (dest && !destination_state(e, dest, &d)) {
208 return NULL;
209 }
210
211
2/2
✓ Branch 0 (10→11) taken 548 times.
✓ Branch 1 (10→12) taken 1340 times.
1888 emit = emit ? str_intern(emit) : NULL;
212
213 1888 if (
214 1888 type != COND_HEREDOCEND
215
2/2
✓ Branch 0 (12→13) taken 1773 times.
✓ Branch 1 (12→15) taken 115 times.
1888 && type != COND_INLIST
216
1/2
✓ Branch 0 (13→14) taken 1773 times.
✗ Branch 1 (13→15) not taken.
1773 && type != COND_INLIST_BUFFER
217 ) {
218 1773 lint_emit_name(&e->err, e->syn.flags, emit, d);
219 }
220
221 1888 Condition *c = xcalloc1(sizeof(*c));
222 1888 c->a.destination = d;
223 1888 c->a.emit_name = emit;
224 1888 c->type = type;
225 1888 ptr_array_append(&e->syn.current_state->conds, c);
226 1888 return c;
227 }
228
229 18 static bool cmd_bufis(EditorState *e, const CommandArgs *a)
230 {
231 18 const char *str = a->args[0];
232 18 const size_t len = strlen(str);
233 18 Condition *c;
234
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 18 times.
18 if (unlikely(len > ARRAYLEN(c->u.str.buf))) {
235 return error_msg (
236 &e->err,
237 "Maximum length of string is %zu bytes",
238 ARRAYLEN(c->u.str.buf)
239 );
240 }
241
242 18 ConditionType type = a->flags[0] == 'i' ? COND_BUFIS_ICASE : COND_BUFIS;
243 18 c = add_condition(e, type, a->args[1], a->args[2]);
244
1/2
✓ Branch 0 (5→6) taken 18 times.
✗ Branch 1 (5→7) not taken.
18 if (!c) {
245 return false;
246 }
247
248 18 memcpy(c->u.str.buf, str, len);
249 18 c->u.str.len = len;
250 18 return true;
251 }
252
253 1547 static bool cmd_char(EditorState *e, const CommandArgs *a)
254 {
255 1547 const char *chars = a->args[0];
256
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1547 times.
1547 if (unlikely(chars[0] == '\0')) {
257 return error_msg(&e->err, "char argument can't be empty");
258 }
259
260 1547 bool add_to_buffer = cmdargs_has_flag(a, 'b');
261 1547 bool invert = cmdargs_has_flag(a, 'n');
262 1547 ConditionType type;
263
2/2
✓ Branch 0 (6→7) taken 1312 times.
✓ Branch 1 (6→10) taken 235 times.
1547 if (add_to_buffer) {
264 type = COND_CHAR_BUFFER;
265
4/4
✓ Branch 0 (7→8) taken 1281 times.
✓ Branch 1 (7→9) taken 31 times.
✓ Branch 2 (8→9) taken 534 times.
✓ Branch 3 (8→10) taken 747 times.
1312 } else if (!invert && chars[1] == '\0') {
266 type = COND_CHAR1;
267 } else {
268 565 type = COND_CHAR;
269 }
270
271 1547 Condition *c = add_condition(e, type, a->args[1], a->args[2]);
272
1/2
✓ Branch 0 (11→12) taken 1547 times.
✗ Branch 1 (11→17) not taken.
1547 if (!c) {
273 return false;
274 }
275
276
2/2
✓ Branch 0 (12→13) taken 747 times.
✓ Branch 1 (12→14) taken 800 times.
1547 if (type == COND_CHAR1) {
277 747 c->u.ch = (unsigned char)chars[0];
278 } else {
279 800 bitset_add_char_range(c->u.bitset, chars);
280
2/2
✓ Branch 0 (15→16) taken 44 times.
✓ Branch 1 (15→17) taken 756 times.
800 if (invert) {
281 44 BITSET_INVERT(c->u.bitset);
282 }
283 }
284
285 return true;
286 }
287
288 66 static bool cmd_default(EditorState *e, const CommandArgs *a)
289 {
290 66 SyntaxLoader *syn = &e->syn;
291 66 ErrorBuffer *ebuf = &e->err;
292
2/4
✓ Branch 0 (3→4) taken 66 times.
✗ Branch 1 (3→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 66 times.
66 if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) {
293 return false;
294 }
295
296 66 const char *value = str_intern(a->args[0]);
297 66 HashMap *map = &syn->current_syntax->default_styles;
298
2/2
✓ Branch 0 (14→9) taken 78 times.
✓ Branch 1 (14→15) taken 66 times.
144 for (size_t i = 1, n = a->nr_args; i < n; i++) {
299 78 const char *name = a->args[i];
300 78 const void *oldval = hashmap_insert_or_replace(map, xstrdup(name), (char*)value);
301
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 78 times.
78 if (unlikely(oldval)) {
302 error_msg(ebuf, "'%s' argument specified multiple times", name);
303 }
304 }
305
306 return true;
307 }
308
309 377 static bool cmd_eat(EditorState *e, const CommandArgs *a)
310 {
311 377 SyntaxLoader *syn = &e->syn;
312
1/2
✓ Branch 0 (3→4) taken 377 times.
✗ Branch 1 (3→10) not taken.
377 if (!in_state(syn, &e->err)) {
313 return false;
314 }
315
316 377 const char *dest = a->args[0];
317 377 State *curstate = syn->current_state;
318
1/2
✓ Branch 0 (5→6) taken 377 times.
✗ Branch 1 (5→10) not taken.
377 if (!destination_state(e, dest, &curstate->default_action.destination)) {
319 return false;
320 }
321
322
2/2
✓ Branch 0 (6→7) taken 90 times.
✓ Branch 1 (6→8) taken 287 times.
377 const char *emit = a->args[1] ? str_intern(a->args[1]) : NULL;
323 377 lint_emit_name(&e->err, syn->flags, emit, curstate->default_action.destination);
324 377 curstate->default_action.emit_name = emit;
325 377 curstate->type = STATE_EAT;
326 377 syn->current_state = NULL;
327 377 return true;
328 }
329
330 13 static bool cmd_heredocbegin(EditorState *e, const CommandArgs *a)
331 {
332 13 SyntaxLoader *syn = &e->syn;
333
1/2
✓ Branch 0 (3→4) taken 13 times.
✗ Branch 1 (3→9) not taken.
13 if (!in_state(syn, &e->err)) {
334 return false;
335 }
336
337 13 Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, a->args[0]);
338
1/2
✓ Branch 0 (5→6) taken 13 times.
✗ Branch 1 (5→9) not taken.
13 if (!subsyn) {
339 return false;
340 }
341
342 // default_action.destination is used as the return state
343 13 const char *ret = a->args[1];
344
1/2
✓ Branch 0 (7→8) taken 13 times.
✗ Branch 1 (7→9) not taken.
13 if (!destination_state(e, ret, &syn->current_state->default_action.destination)) {
345 return false;
346 }
347
348 13 syn->current_state->default_action.emit_name = NULL;
349 13 syn->current_state->type = STATE_HEREDOCBEGIN;
350 13 syn->current_state->heredoc.subsyntax = subsyn;
351 13 syn->current_state = NULL;
352
353 // Normally merge() marks subsyntax used but in case of heredocs merge()
354 // is not called when syntax file is loaded
355 13 subsyn->used = true;
356 13 return true;
357 }
358
359 10 static bool cmd_heredocend(EditorState *e, const CommandArgs *a)
360 {
361 10 Condition *c = add_condition(e, COND_HEREDOCEND, a->args[0], a->args[1]);
362
1/2
✓ Branch 0 (3→4) taken 10 times.
✗ Branch 1 (3→7) not taken.
10 if (unlikely(!c)) {
363 return false;
364 }
365
366 10 Syntax *current_syntax = e->syn.current_syntax;
367 10 BUG_ON(!current_syntax);
368 10 current_syntax->heredoc = true;
369 10 return true;
370 }
371
372 // Forward declaration, used in cmd_include() and cmd_require()
373 static int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags);
374
375 static bool cmd_include(EditorState *e, const CommandArgs *a)
376 {
377 SyntaxLoadFlags flags = SYN_MUST_EXIST;
378 if (a->flags[0] == 'b') {
379 flags |= SYN_BUILTIN;
380 }
381 int r = read_syntax(e, a->args[0], flags);
382 return r == 0;
383 }
384
385 102 static bool cmd_list(EditorState *e, const CommandArgs *a)
386 {
387 102 SyntaxLoader *syn = &e->syn;
388 102 ErrorBuffer *ebuf = &e->err;
389
2/4
✓ Branch 0 (3→4) taken 102 times.
✗ Branch 1 (3→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 102 times.
102 if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) {
390 return false;
391 }
392
393 102 char **args = a->args;
394 102 const char *name = args[0];
395 102 StringList *list = find_string_list(syn->current_syntax, name);
396
2/2
✓ Branch 0 (8→9) taken 8 times.
✓ Branch 1 (8→12) taken 94 times.
102 if (!list) {
397 8 list = xcalloc1(sizeof(*list));
398 8 hashmap_insert(&syn->current_syntax->string_lists, xstrdup(name), list);
399
1/2
✗ Branch 0 (12→13) not taken.
✓ Branch 1 (12→14) taken 94 times.
94 } else if (unlikely(list->defined)) {
400 return error_msg(ebuf, "List '%s' already exists", name);
401 }
402 102 list->defined = true;
403
404 102 bool icase = a->flags[0] == 'i';
405 102 HashSet *set = &list->strings;
406 102 hashset_init(set, a->nr_args - 1, icase);
407
2/2
✓ Branch 0 (18→16) taken 3607 times.
✓ Branch 1 (18→19) taken 102 times.
3709 for (size_t i = 1, n = a->nr_args; i < n; i++) {
408 3607 const char *str = args[i];
409 3607 hashset_insert(set, str, strlen(str));
410 }
411 return true;
412 }
413
414 105 static bool cmd_inlist(EditorState *e, const CommandArgs *a)
415 {
416 105 char **args = a->args;
417 105 const char *name = args[0];
418
1/2
✓ Branch 0 (3→4) taken 105 times.
✗ Branch 1 (3→5) not taken.
105 ConditionType type = cmdargs_has_flag(a, 'b') ? COND_INLIST_BUFFER : COND_INLIST;
419
2/2
✓ Branch 0 (5→6) taken 27 times.
✓ Branch 1 (5→7) taken 78 times.
132 Condition *c = add_condition(e, type, args[1], args[2] ? args[2] : name);
420
421
1/2
✓ Branch 0 (8→9) taken 105 times.
✗ Branch 1 (8→15) not taken.
105 if (!c) {
422 return false;
423 }
424
425 105 StringList *list = find_string_list(e->syn.current_syntax, name);
426
2/2
✓ Branch 0 (10→11) taken 95 times.
✓ Branch 1 (10→14) taken 10 times.
105 if (unlikely(!list)) {
427 // Add undefined list
428 95 list = xcalloc1(sizeof(*list));
429 95 hashmap_insert(&e->syn.current_syntax->string_lists, xstrdup(name), list);
430 }
431
432 105 list->used = true;
433 105 c->u.str_list = list;
434 105 return true;
435 }
436
437 532 static bool cmd_noeat(EditorState *e, const CommandArgs *a)
438 {
439 532 SyntaxLoader *syn = &e->syn;
440 532 State *dest;
441
2/4
✓ Branch 0 (3→4) taken 532 times.
✗ Branch 1 (3→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 532 times.
532 if (unlikely(!in_state(syn, &e->err) || !destination_state(e, a->args[0], &dest))) {
442 return false;
443 }
444
445 532 State *curstate = syn->current_state;
446
2/2
✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 531 times.
532 if (unlikely(dest == curstate)) {
447 1 return error_msg(&e->err, "using noeat to jump to same state causes infinite loop");
448 }
449
450 531 Action *defaction = &curstate->default_action;
451 531 bool bflag = cmdargs_has_flag(a, 'b');
452 531 defaction->destination = dest;
453 531 defaction->emit_name = NULL;
454
2/2
✓ Branch 0 (10→11) taken 504 times.
✓ Branch 1 (10→12) taken 27 times.
531 curstate->type = bflag ? STATE_NOEAT_BUFFER : STATE_NOEAT;
455 531 syn->current_state = NULL;
456 531 return true;
457 }
458
459 90 static bool cmd_recolor(EditorState *e, const CommandArgs *a)
460 {
461 // If length is not specified then buffered bytes will be recolored
462 90 ConditionType type = COND_RECOLOR_BUFFER;
463 90 size_t len = 0;
464
465 90 const char *len_str = a->args[1];
466
2/2
✓ Branch 0 (2→3) taken 58 times.
✓ Branch 1 (2→8) taken 32 times.
90 if (len_str) {
467 58 type = COND_RECOLOR;
468
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 58 times.
58 if (unlikely(!str_to_size(len_str, &len))) {
469 return error_msg(&e->err, "invalid number: '%s'", len_str);
470 }
471
1/2
✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→8) taken 58 times.
58 if (unlikely(len < 1 || len > 2500)) {
472 return error_msg(&e->err, "number of bytes must be between 1-2500 (got %zu)", len);
473 }
474 }
475
476 90 Condition *c = add_condition(e, type, NULL, a->args[0]);
477
1/2
✓ Branch 0 (9→10) taken 90 times.
✗ Branch 1 (9→12) not taken.
90 if (!c) {
478 return false;
479 }
480
481
2/2
✓ Branch 0 (10→11) taken 58 times.
✓ Branch 1 (10→12) taken 32 times.
90 if (type == COND_RECOLOR) {
482 58 c->u.recolor_len = len;
483 }
484
485 return true;
486 }
487
488 17 static bool cmd_require(EditorState *e, const CommandArgs *a)
489 {
490 17 char buf[8192];
491 17 char *path;
492 17 size_t path_len;
493 17 HashSet *set;
494 17 SyntaxLoadFlags flags = SYN_MUST_EXIST;
495
496
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 17 times.
17 if (a->flags[0] == 'f') {
497 set = &e->required_syntax_files;
498 path = a->args[0];
499 path_len = strlen(path);
500 } else {
501 17 set = &e->required_syntax_builtins;
502 17 path_len = xsnprintf(buf, sizeof(buf), "syntax/inc/%s", a->args[0]);
503 17 path = buf;
504 17 flags |= SYN_BUILTIN;
505 }
506
507
2/2
✓ Branch 0 (6→7) taken 5 times.
✓ Branch 1 (6→11) taken 12 times.
17 if (hashset_get(set, path, path_len)) {
508 return true;
509 }
510
511 5 SyntaxLoader *syn = &e->syn;
512 5 const SyntaxLoadFlags save = syn->flags;
513 5 syn->flags &= ~SYN_WARN_ON_UNUSED_SUBSYN;
514 5 int r = read_syntax(e, path, flags);
515 5 syn->flags = save;
516
1/2
✓ Branch 0 (8→9) taken 5 times.
✗ Branch 1 (8→11) not taken.
5 if (r != 0) {
517 return false;
518 }
519
520 5 hashset_insert(set, path, path_len);
521 5 return true;
522 }
523
524 923 static bool cmd_state(EditorState *e, const CommandArgs *a)
525 {
526 923 SyntaxLoader *syn = &e->syn;
527 923 ErrorBuffer *ebuf = &e->err;
528
2/4
✓ Branch 0 (3→4) taken 923 times.
✗ Branch 1 (3→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 923 times.
923 if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) {
529 return false;
530 }
531
532 923 const char *name = a->args[0];
533
2/4
✓ Branch 0 (7→8) taken 923 times.
✗ Branch 1 (7→9) not taken.
✗ Branch 2 (8→9) not taken.
✓ Branch 3 (8→10) taken 923 times.
923 if (unlikely(streq(name, "END") || streq(name, "this"))) {
534 return error_msg(ebuf, "'%s' is reserved state name", name);
535 }
536
537 923 State *state = find_or_add_state(syn, name);
538
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 923 times.
923 if (unlikely(state->defined)) {
539 return error_msg(ebuf, "State '%s' already exists", name);
540 }
541
542 923 const char *emit_name = a->args[1];
543
3/6
✓ Branch 0 (13→14) taken 16 times.
✓ Branch 1 (13→20) taken 907 times.
✓ Branch 2 (14→15) taken 16 times.
✗ Branch 3 (14→16) not taken.
✗ Branch 4 (16→17) not taken.
✗ Branch 5 (16→19) not taken.
923 if ((syn->flags & SYN_LINT) && emit_name && streq(emit_name, name)) {
544 error_msg(ebuf, "Redundant emit-name '%s'", emit_name);
545 }
546
547 923 state->defined = true;
548
2/2
✓ Branch 0 (20→21) taken 282 times.
✓ Branch 1 (20→22) taken 625 times.
1205 state->emit_name = str_intern(emit_name ? emit_name : name);
549 923 syn->current_state = state;
550 923 return true;
551 }
552
553 118 static bool cmd_str(EditorState *e, const CommandArgs *a)
554 {
555 118 const char *str = a->args[0];
556 118 size_t len = strlen(str);
557
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 118 times.
118 if (unlikely(len < 2)) {
558 return error_msg(&e->err, "string should be at least 2 bytes; use 'char' for single bytes");
559 }
560
561 118 Condition *c;
562 118 size_t maxlen = ARRAYLEN(c->u.str.buf);
563
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 118 times.
118 if (unlikely(len > maxlen)) {
564 return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen);
565 }
566
567 118 ConditionType type;
568
2/2
✓ Branch 0 (7→8) taken 111 times.
✓ Branch 1 (7→10) taken 7 times.
118 if (cmdargs_has_flag(a, 'i')) {
569 type = COND_STR_ICASE;
570 } else {
571
2/2
✓ Branch 0 (8→9) taken 36 times.
✓ Branch 1 (8→10) taken 75 times.
111 type = (len == 2) ? COND_STR2 : COND_STR;
572 }
573
574 118 c = add_condition(e, type, a->args[1], a->args[2]);
575
1/2
✓ Branch 0 (11→12) taken 118 times.
✗ Branch 1 (11→13) not taken.
118 if (!c) {
576 return false;
577 }
578
579 118 memcpy(c->u.str.buf, str, len);
580 118 c->u.str.len = len;
581 118 return true;
582 }
583
584 151 static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes)
585 {
586 151 Syntax *syntax = syn->current_syntax;
587 151 BUG_ON(!syntax);
588
4/4
✓ Branch 0 (5→6) taken 150 times.
✓ Branch 1 (5→8) taken 1 times.
✓ Branch 2 (7→8) taken 5 times.
✓ Branch 3 (7→9) taken 145 times.
151 bool r = close_state(syn, ebuf) && finalize_syntax(syntaxes, syntax, ebuf);
589 6 if (!r) {
590 6 free_syntax(syntax);
591 }
592 151 syn->current_syntax = NULL;
593 151 return r;
594 }
595
596 152 static bool cmd_syntax(EditorState *e, const CommandArgs *a)
597 {
598 152 SyntaxLoader *syn = &e->syn;
599
3/4
✓ Branch 0 (2→3) taken 83 times.
✓ Branch 1 (2→5) taken 69 times.
✓ Branch 2 (4→5) taken 83 times.
✗ Branch 3 (4→11) not taken.
152 if (syn->current_syntax && !finish_syntax(syn, &e->err, &e->syntaxes)) {
600 return false;
601 }
602
603 152 Syntax *syntax = xcalloc1(sizeof(*syntax));
604 152 syntax->name = xstrdup(a->args[0]);
605
4/4
✓ Branch 0 (7→8) taken 82 times.
✓ Branch 1 (7→10) taken 70 times.
✓ Branch 2 (8→9) taken 7 times.
✓ Branch 3 (8→10) taken 75 times.
152 if (is_subsyntax(syntax) && !(syn->flags & SYN_WARN_ON_UNUSED_SUBSYN)) {
606 7 syntax->warned_unused_subsyntax = true;
607 }
608
609 152 syn->current_syntax = syntax;
610 152 syn->current_state = NULL;
611 152 return true;
612 }
613
614 #define CMD(name, flags, min, max, func) \
615 {name, flags, CMDOPT_ALLOW_IN_RC, min, max, func}
616
617 static const Command cmds[] = {
618 CMD("bufis", "i", 2, 3, cmd_bufis),
619 CMD("char", "bn", 2, 3, cmd_char),
620 CMD("default", "", 2, -1, cmd_default),
621 CMD("eat", "", 1, 2, cmd_eat),
622 CMD("heredocbegin", "", 2, 2, cmd_heredocbegin),
623 CMD("heredocend", "", 1, 2, cmd_heredocend),
624 CMD("include", "b", 1, 1, cmd_include),
625 CMD("inlist", "b", 2, 3, cmd_inlist),
626 CMD("list", "i", 2, -1, cmd_list),
627 CMD("noeat", "b", 1, 1, cmd_noeat),
628 CMD("recolor", "", 1, 2, cmd_recolor),
629 CMD("require", "f", 1, 1, cmd_require),
630 CMD("state", "", 1, 2, cmd_state),
631 CMD("str", "i", 2, 3, cmd_str),
632 CMD("syntax", "", 1, 1, cmd_syntax),
633 };
634
635 24 UNITTEST {
636 24 CHECK_BSEARCH_ARRAY(cmds, name);
637 24 }
638
639 4070 static const Command *find_syntax_command(const char *name)
640 {
641 4070 return BSEARCH(name, cmds, command_cmp);
642 }
643
644 static char *expand_syntax_var(const EditorState *e, const char *name)
645 {
646 if (streq(name, "DTE_HOME")) {
647 return xstrdup(e->user_config_dir);
648 }
649 return NULL;
650 }
651
652 static const CommandSet syntax_commands = {
653 .lookup = find_syntax_command,
654 };
655
656 75 static CommandRunner cmdrunner_for_syntaxes(EditorState *e)
657 {
658 75 CommandRunner runner = cmdrunner(e, &syntax_commands);
659 75 runner.expand_variable = expand_syntax_var;
660 75 runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR;
661 75 return runner;
662 }
663
664 5 static ConfigFlags syn_flags_to_cfg_flags(SyntaxLoadFlags flags)
665 {
666 5 static_assert(SYN_MUST_EXIST == (SyntaxLoadFlags)CFG_MUST_EXIST);
667 5 static_assert(SYN_BUILTIN == (SyntaxLoadFlags)CFG_BUILTIN);
668 5 SyntaxLoadFlags mask = SYN_MUST_EXIST | SYN_BUILTIN;
669 5 return (ConfigFlags)(flags & mask);
670 }
671
672 5 static int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags)
673 {
674 5 CommandRunner runner = cmdrunner_for_syntaxes(e);
675 5 return read_config(&runner, filename, syn_flags_to_cfg_flags(flags));
676 }
677
678 70 Syntax *load_syntax (
679 EditorState *e,
680 StringView config_text,
681 const char *config_filename,
682 SyntaxLoadFlags flags
683 ) {
684 70 SyntaxLoader *syn = &e->syn;
685 70 *syn = (SyntaxLoader) {
686 .current_syntax = NULL,
687 .current_state = NULL,
688 70 .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN,
689 };
690
691 70 ErrorBuffer *ebuf = &e->err;
692 70 const char *saved_file = ebuf->config_filename;
693 70 const unsigned int saved_line = ebuf->config_line;
694 70 CommandRunner runner = cmdrunner_for_syntaxes(e);
695
696 70 ebuf->config_filename = config_filename;
697 70 ebuf->config_line = 1;
698 70 bool r = exec_config(&runner, config_text);
699
700
2/2
✓ Branch 0 (3→4) taken 69 times.
✓ Branch 1 (3→10) taken 1 times.
70 if (syn->current_syntax) {
701
2/2
✓ Branch 0 (4→5) taken 68 times.
✓ Branch 1 (4→8) taken 1 times.
69 if (r) {
702 68 r = finish_syntax(syn, ebuf, &e->syntaxes);
703
2/2
✓ Branch 0 (6→7) taken 62 times.
✓ Branch 1 (6→10) taken 6 times.
68 if (r) {
704 62 find_unused_subsyntaxes(&e->syntaxes, ebuf);
705 }
706 } else {
707 1 free_syntax(syn->current_syntax);
708 1 syn->current_syntax = NULL;
709 }
710 }
711
712 70 ebuf->config_filename = saved_file;
713 70 ebuf->config_line = saved_line;
714
715
2/2
✓ Branch 0 (10→11) taken 63 times.
✓ Branch 1 (10→17) taken 7 times.
70 if (!r) {
716 return NULL;
717 }
718
719 63 const char *base = path_basename(config_filename);
720 63 Syntax *syntax = find_syntax(&e->syntaxes, base);
721
2/2
✓ Branch 0 (12→13) taken 2 times.
✓ Branch 1 (12→15) taken 61 times.
63 if (!syntax) {
722 2 error_msg(&e->err, "%s: no main syntax found (i.e. with name '%s')", config_filename, base);
723 2 return NULL;
724 }
725
726
1/2
✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 61 times.
61 if (e->status != EDITOR_INITIALIZING) {
727 update_syntax_styles(syntax, &e->styles);
728 }
729
730 return syntax;
731 }
732
733 55 static Syntax *load_syntax_builtin(EditorState *e, const char *name, SyntaxLoadFlags flags)
734 {
735 55 const BuiltinConfig *cfg = get_builtin_config(name);
736
1/2
✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→6) not taken.
55 if (!cfg) {
737
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 55 times.
55 if (flags & SYN_MUST_EXIST) {
738 error_msg(&e->err, "no built-in config with name '%s'", name);
739 }
740 55 return NULL;
741 }
742 return load_syntax(e, cfg->text, name, flags | SYN_BUILTIN);
743 }
744
745 57 Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags)
746 {
747 57 char *alloc;
748 57 ssize_t size = read_file(filename, &alloc, 0);
749
2/2
✓ Branch 0 (3→4) taken 55 times.
✓ Branch 1 (3→10) taken 2 times.
57 if (size < 0) {
750 55 int err = errno;
751
2/4
✓ Branch 0 (4→5) taken 55 times.
✗ Branch 1 (4→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→9) taken 55 times.
55 if (err != ENOENT || (flags & SYN_MUST_EXIST)) {
752 error_msg(&e->err, "Error reading %s: %s", filename, strerror(err));
753 errno = err;
754 }
755 55 return NULL;
756 }
757
758 2 StringView config = string_view(alloc, size);
759 2 Syntax *syntax = load_syntax(e, config, filename, flags);
760 2 free(alloc);
761
762
2/2
✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 1 times.
2 if (unlikely(!syntax)) {
763 1 errno = EINVAL;
764 }
765
766 return syntax;
767 }
768
769 55 Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype)
770 {
771
1/2
✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→8) not taken.
55 if (!is_valid_filetype_name(filetype)) {
772 return NULL;
773 }
774
775 55 const char *cfgdir = e->user_config_dir;
776 55 char filename[8192];
777 55 xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype);
778
779 55 Syntax *syn = load_syntax_file(e, filename, 0);
780
2/4
✓ Branch 0 (5→6) taken 55 times.
✗ Branch 1 (5→8) not taken.
✓ Branch 2 (6→7) taken 55 times.
✗ Branch 3 (6→8) not taken.
55 if (syn || errno != ENOENT) {
781 return syn;
782 }
783
784 // Skip past "%s/" (cfgdir) part of formatted string from above and
785 // try to load a built-in syntax named `syntax/<filetype>`
786 55 const char *builtin_name = filename + strlen(cfgdir) + STRLEN("/");
787 55 return load_syntax_builtin(e, builtin_name, 0);
788 }
789