dte test coverage


Directory: ./
File: src/syntax/state.c
Date: 2025-07-13 15:27:15
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/path.h"
18 #include "util/readfile.h"
19 #include "util/strtonum.h"
20 #include "util/xmalloc.h"
21 #include "util/xsnprintf.h"
22 #include "util/xstring.h"
23
24 3901 static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
25 {
26
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");
27 }
28
29 2810 static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf)
30 {
31
1/2
✓ Branch 0 (3→4) taken 2810 times.
✗ Branch 1 (3→9) not taken.
2810 if (unlikely(!in_syntax(syn, ebuf))) {
32 return false;
33 }
34
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");
35 }
36
37 1242 static bool close_state(SyntaxLoader *syn, ErrorBuffer *ebuf)
38 {
39 1242 const State *state = syn->current_state;
40
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→5) taken 1241 times.
1242 if (!state) {
41 return true;
42 }
43
44 1 syn->current_state = NULL;
45
1/2
✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
1 if (likely(state->type != STATE_INVALID)) {
46 return true;
47 }
48
49 // This error applies to the state itself rather than the last command, so
50 // it doesn't make sense to include the command name in the error message
51 1 const char *name = state->name;
52 1 return error_msg_for_cmd(ebuf, NULL, "No default action in state '%s'", name);
53 }
54
55 2714 static State *find_or_add_state(const SyntaxLoader *syn, const char *name)
56 {
57 2714 State *state = find_state(syn->current_syntax, name);
58
2/2
✓ Branch 0 (3→4) taken 924 times.
✓ Branch 1 (3→9) taken 1790 times.
2714 if (state) {
59 return state;
60 }
61
62 924 state = xcalloc1(sizeof(*state));
63 924 state->name = xstrdup(name);
64 924 state->defined = false;
65 924 state->type = STATE_INVALID;
66
67
2/2
✓ Branch 0 (6→7) taken 151 times.
✓ Branch 1 (6→8) taken 773 times.
924 if (syn->current_syntax->states.count == 0) {
68 151 syn->current_syntax->start_state = state;
69 }
70
71 924 return hashmap_insert(&syn->current_syntax->states, state->name, state);
72 }
73
74 2448 static State *reference_state (
75 const SyntaxLoader *syn,
76 ErrorBuffer *ebuf,
77 const char *name
78 ) {
79
2/2
✓ Branch 0 (2→3) taken 657 times.
✓ Branch 1 (2→4) taken 1791 times.
2448 if (streq(name, "this")) {
80 657 return syn->current_state;
81 }
82
83 1791 State *state = find_or_add_state(syn, name);
84
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)) {
85 1 error_msg ( // Soft error
86 ebuf,
87 "destination '%s' can be optimized to 'this' in '%s' syntax",
88 name,
89 1 syn->current_syntax->name
90 );
91 }
92
93 return state;
94 }
95
96 272 static bool in_subsyntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
97 {
98 272 bool ss = likely(is_subsyntax(syn->current_syntax));
99
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");
100 }
101
102 188 static Syntax *must_find_subsyntax (
103 const HashMap *syntaxes,
104 ErrorBuffer *ebuf,
105 const char *name
106 ) {
107 188 Syntax *syntax = find_any_syntax(syntaxes, name);
108
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 188 times.
188 if (unlikely(!syntax)) {
109 error_msg(ebuf, "No such syntax '%s'", name);
110 return NULL;
111 }
112
1/2
✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 188 times.
188 if (unlikely(!is_subsyntax(syntax))) {
113 error_msg(ebuf, "Syntax '%s' is not a subsyntax", name);
114 return NULL;
115 }
116 return syntax;
117 }
118
119 175 static bool subsyntax_call (
120 EditorState *e,
121 const char *name,
122 const char *ret,
123 State **dest
124 ) {
125 175 Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, name);
126
127 175 SyntaxMerge m = {
128 .subsyn = subsyn,
129 .return_state = NULL,
130 .delim = NULL,
131 .delim_len = 0,
132 };
133
134
2/2
✓ Branch 0 (3→4) taken 17 times.
✓ Branch 1 (3→6) taken 158 times.
175 if (streq(ret, "END")) {
135
1/2
✓ Branch 0 (5→9) taken 17 times.
✗ Branch 1 (5→12) not taken.
17 if (!in_subsyntax(&e->syn, &e->err)) {
136 return false;
137 }
138
1/2
✓ Branch 0 (6→7) taken 158 times.
✗ Branch 1 (6→12) not taken.
158 } else if (subsyn) {
139 158 m.return_state = reference_state(&e->syn, &e->err, ret);
140 }
141
142
1/2
✓ Branch 0 (9→10) taken 175 times.
✗ Branch 1 (9→12) not taken.
175 if (subsyn) {
143 175 *dest = merge_syntax(e->syn.current_syntax, &m, &e->styles);
144 175 return true;
145 }
146
147 return false;
148 }
149
150 2720 static bool destination_state(EditorState *e, const char *name, State **dest)
151 {
152 2720 const char *sep = strchr(name, ':');
153
2/2
✓ Branch 0 (2→3) taken 175 times.
✓ Branch 1 (2→6) taken 2545 times.
2720 if (sep) {
154 // subsyntax:returnstate
155 175 char *sub = xstrcut(name, sep - name);
156 175 bool success = subsyntax_call(e, sub, sep + 1, dest);
157 175 free(sub);
158 175 return success;
159 }
160
161
2/2
✓ Branch 0 (6→7) taken 255 times.
✓ Branch 1 (6→10) taken 2290 times.
2545 if (streq(name, "END")) {
162
1/2
✓ Branch 0 (8→9) taken 255 times.
✗ Branch 1 (8→12) not taken.
255 if (!in_subsyntax(&e->syn, &e->err)) {
163 return false;
164 }
165 255 *dest = NULL;
166 255 return true;
167 }
168
169 2290 *dest = reference_state(&e->syn, &e->err, name);
170 2290 return true;
171 }
172
173 2150 static void lint_emit_name (
174 ErrorBuffer *ebuf,
175 SyntaxLoadFlags flags,
176 const char *ename,
177 const State *dest
178 ) {
179 2150 if (
180
2/2
✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→7) taken 2136 times.
2150 (flags & SYN_LINT)
181 14 && ename
182
2/2
✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→7) taken 13 times.
14 && dest
183
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→7) not taken.
1 && dest->emit_name
184
1/2
✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→7) not taken.
1 && interned_strings_equal(ename, dest->emit_name)
185 ) {
186 1 error_msg ( // Soft error
187 ebuf,
188 "emit-name '%s' not needed (destination state uses same emit-name)",
189 ename
190 );
191 }
192 2150 }
193
194 1888 static Condition *add_condition (
195 EditorState *e,
196 ConditionType type,
197 const char *dest,
198 const char *emit
199 ) {
200 1888 BUG_ON(!dest && cond_type_has_destination(type));
201
1/2
✓ Branch 0 (6→7) taken 1888 times.
✗ Branch 1 (6→18) not taken.
1888 if (!in_state(&e->syn, &e->err)) {
202 return NULL;
203 }
204
205 1888 State *d = NULL;
206
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)) {
207 return NULL;
208 }
209
210
2/2
✓ Branch 0 (10→11) taken 548 times.
✓ Branch 1 (10→12) taken 1340 times.
1888 emit = emit ? str_intern(emit) : NULL;
211
212 1888 if (
213 1888 type != COND_HEREDOCEND
214
2/2
✓ Branch 0 (12→13) taken 1773 times.
✓ Branch 1 (12→15) taken 115 times.
1888 && type != COND_INLIST
215
1/2
✓ Branch 0 (13→14) taken 1773 times.
✗ Branch 1 (13→15) not taken.
1773 && type != COND_INLIST_BUFFER
216 ) {
217 1773 lint_emit_name(&e->err, e->syn.flags, emit, d);
218 }
219
220 1888 Condition *c = xcalloc1(sizeof(*c));
221 1888 c->a.destination = d;
222 1888 c->a.emit_name = emit;
223 1888 c->type = type;
224 1888 ptr_array_append(&e->syn.current_state->conds, c);
225 1888 return c;
226 }
227
228 18 static bool cmd_bufis(EditorState *e, const CommandArgs *a)
229 {
230 18 const char *str = a->args[0];
231 18 const size_t len = strlen(str);
232 18 Condition *c;
233
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 18 times.
18 if (unlikely(len > ARRAYLEN(c->u.str.buf))) {
234 return error_msg (
235 &e->err,
236 "Maximum length of string is %zu bytes",
237 ARRAYLEN(c->u.str.buf)
238 );
239 }
240
241 18 ConditionType type = a->flags[0] == 'i' ? COND_BUFIS_ICASE : COND_BUFIS;
242 18 c = add_condition(e, type, a->args[1], a->args[2]);
243
1/2
✓ Branch 0 (5→6) taken 18 times.
✗ Branch 1 (5→7) not taken.
18 if (!c) {
244 return false;
245 }
246
247 18 memcpy(c->u.str.buf, str, len);
248 18 c->u.str.len = len;
249 18 return true;
250 }
251
252 1547 static bool cmd_char(EditorState *e, const CommandArgs *a)
253 {
254 1547 const char *chars = a->args[0];
255
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1547 times.
1547 if (unlikely(chars[0] == '\0')) {
256 return error_msg(&e->err, "char argument can't be empty");
257 }
258
259 1547 bool add_to_buffer = cmdargs_has_flag(a, 'b');
260 1547 bool invert = cmdargs_has_flag(a, 'n');
261 1547 ConditionType type;
262
2/2
✓ Branch 0 (6→7) taken 1312 times.
✓ Branch 1 (6→10) taken 235 times.
1547 if (add_to_buffer) {
263 type = COND_CHAR_BUFFER;
264
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') {
265 type = COND_CHAR1;
266 } else {
267 565 type = COND_CHAR;
268 }
269
270 1547 Condition *c = add_condition(e, type, a->args[1], a->args[2]);
271
1/2
✓ Branch 0 (11→12) taken 1547 times.
✗ Branch 1 (11→17) not taken.
1547 if (!c) {
272 return false;
273 }
274
275
2/2
✓ Branch 0 (12→13) taken 747 times.
✓ Branch 1 (12→14) taken 800 times.
1547 if (type == COND_CHAR1) {
276 747 c->u.ch = (unsigned char)chars[0];
277 } else {
278 800 bitset_add_char_range(c->u.bitset, chars);
279
2/2
✓ Branch 0 (15→16) taken 44 times.
✓ Branch 1 (15→17) taken 756 times.
800 if (invert) {
280 44 BITSET_INVERT(c->u.bitset);
281 }
282 }
283
284 return true;
285 }
286
287 66 static bool cmd_default(EditorState *e, const CommandArgs *a)
288 {
289 66 SyntaxLoader *syn = &e->syn;
290 66 ErrorBuffer *ebuf = &e->err;
291
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)) {
292 return false;
293 }
294
295 66 const char *value = str_intern(a->args[0]);
296 66 HashMap *map = &syn->current_syntax->default_styles;
297
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++) {
298 78 const char *name = a->args[i];
299 78 const void *oldval = hashmap_insert_or_replace(map, xstrdup(name), (char*)value);
300
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 78 times.
78 if (unlikely(oldval)) {
301 // Soft error
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 3640 times.
✓ Branch 1 (18→19) taken 102 times.
3742 for (size_t i = 1, n = a->nr_args; i < n; i++) {
408 3640 const char *str = args[i];
409 3640 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 // Soft error
545 error_msg(ebuf, "Redundant emit-name '%s'", emit_name);
546 }
547
548 923 state->defined = true;
549
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);
550 923 syn->current_state = state;
551 923 return true;
552 }
553
554 118 static bool cmd_str(EditorState *e, const CommandArgs *a)
555 {
556 118 const char *str = a->args[0];
557 118 size_t len = strlen(str);
558
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 118 times.
118 if (unlikely(len < 2)) {
559 error_msg ( // Soft error
560 &e->err,
561 "string should be at least 2 bytes; use 'char' for single bytes"
562 );
563 }
564
565 118 Condition *c;
566 118 size_t maxlen = ARRAYLEN(c->u.str.buf);
567
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 118 times.
118 if (unlikely(len > maxlen)) {
568 return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen);
569 }
570
571 118 ConditionType type;
572
2/2
✓ Branch 0 (7→8) taken 111 times.
✓ Branch 1 (7→10) taken 7 times.
118 if (cmdargs_has_flag(a, 'i')) {
573 type = COND_STR_ICASE;
574 } else {
575
2/2
✓ Branch 0 (8→9) taken 36 times.
✓ Branch 1 (8→10) taken 75 times.
111 type = (len == 2) ? COND_STR2 : COND_STR;
576 }
577
578 118 c = add_condition(e, type, a->args[1], a->args[2]);
579
1/2
✓ Branch 0 (11→12) taken 118 times.
✗ Branch 1 (11→13) not taken.
118 if (!c) {
580 return false;
581 }
582
583 118 memcpy(c->u.str.buf, str, len);
584 118 c->u.str.len = len;
585 118 return true;
586 }
587
588 151 static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes)
589 {
590 151 Syntax *syntax = syn->current_syntax;
591 151 BUG_ON(!syntax);
592
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);
593 6 if (!r) {
594 6 free_syntax(syntax);
595 }
596 151 syn->current_syntax = NULL;
597 151 return r;
598 }
599
600 152 static bool cmd_syntax(EditorState *e, const CommandArgs *a)
601 {
602 152 SyntaxLoader *syn = &e->syn;
603
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)) {
604 return false;
605 }
606
607 152 Syntax *syntax = xcalloc1(sizeof(*syntax));
608 152 syntax->name = xstrdup(a->args[0]);
609
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)) {
610 7 syntax->warned_unused_subsyntax = true;
611 }
612
613 152 syn->current_syntax = syntax;
614 152 syn->current_state = NULL;
615 152 return true;
616 }
617
618 #define CMD(name, flags, min, max, func) \
619 {name, flags, CMDOPT_ALLOW_IN_RC, min, max, func}
620
621 static const Command cmds[] = {
622 CMD("bufis", "i", 2, 3, cmd_bufis),
623 CMD("char", "bn", 2, 3, cmd_char),
624 CMD("default", "", 2, -1, cmd_default),
625 CMD("eat", "", 1, 2, cmd_eat),
626 CMD("heredocbegin", "", 2, 2, cmd_heredocbegin),
627 CMD("heredocend", "", 1, 2, cmd_heredocend),
628 CMD("include", "b", 1, 1, cmd_include),
629 CMD("inlist", "b", 2, 3, cmd_inlist),
630 CMD("list", "i", 2, -1, cmd_list),
631 CMD("noeat", "b", 1, 1, cmd_noeat),
632 CMD("recolor", "", 1, 2, cmd_recolor),
633 CMD("require", "f", 1, 1, cmd_require),
634 CMD("state", "", 1, 2, cmd_state),
635 CMD("str", "i", 2, 3, cmd_str),
636 CMD("syntax", "", 1, 1, cmd_syntax),
637 };
638
639 24 UNITTEST {
640 24 CHECK_BSEARCH_ARRAY(cmds, name);
641 24 }
642
643 4070 static const Command *find_syntax_command(const char *name)
644 {
645 4070 return BSEARCH(name, cmds, command_cmp);
646 }
647
648 static char *expand_syntax_var(const EditorState *e, const char *name)
649 {
650 if (streq(name, "DTE_HOME")) {
651 return xstrdup(e->user_config_dir);
652 }
653 return NULL;
654 }
655
656 static const CommandSet syntax_commands = {
657 .lookup = find_syntax_command,
658 };
659
660 75 static CommandRunner cmdrunner_for_syntaxes(EditorState *e)
661 {
662 75 CommandRunner runner = cmdrunner(e, &syntax_commands);
663 75 runner.expand_variable = expand_syntax_var;
664 75 runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR;
665 75 return runner;
666 }
667
668 5 static ConfigFlags syn_flags_to_cfg_flags(SyntaxLoadFlags flags)
669 {
670 5 static_assert(SYN_MUST_EXIST == (SyntaxLoadFlags)CFG_MUST_EXIST);
671 5 static_assert(SYN_BUILTIN == (SyntaxLoadFlags)CFG_BUILTIN);
672 5 SyntaxLoadFlags mask = SYN_MUST_EXIST | SYN_BUILTIN;
673 5 return (ConfigFlags)(flags & mask);
674 }
675
676 5 static int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags)
677 {
678 5 CommandRunner runner = cmdrunner_for_syntaxes(e);
679 5 return read_config(&runner, filename, syn_flags_to_cfg_flags(flags));
680 }
681
682 70 Syntax *load_syntax (
683 EditorState *e,
684 StringView config_text,
685 const char *config_filename,
686 SyntaxLoadFlags flags
687 ) {
688 70 SyntaxLoader *syn = &e->syn;
689 70 *syn = (SyntaxLoader) {
690 .current_syntax = NULL,
691 .current_state = NULL,
692 70 .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN,
693 };
694
695 70 ErrorBuffer *ebuf = &e->err;
696 70 const char *saved_file = ebuf->config_filename;
697 70 const unsigned int saved_line = ebuf->config_line;
698 70 CommandRunner runner = cmdrunner_for_syntaxes(e);
699
700 70 ebuf->config_filename = config_filename;
701 70 ebuf->config_line = 1;
702 70 bool r = exec_config(&runner, config_text);
703
704
2/2
✓ Branch 0 (3→4) taken 69 times.
✓ Branch 1 (3→10) taken 1 times.
70 if (syn->current_syntax) {
705
2/2
✓ Branch 0 (4→5) taken 68 times.
✓ Branch 1 (4→8) taken 1 times.
69 if (r) {
706 68 r = finish_syntax(syn, ebuf, &e->syntaxes);
707
2/2
✓ Branch 0 (6→7) taken 62 times.
✓ Branch 1 (6→10) taken 6 times.
68 if (r) {
708 62 find_unused_subsyntaxes(&e->syntaxes, ebuf);
709 }
710 } else {
711 1 free_syntax(syn->current_syntax);
712 1 syn->current_syntax = NULL;
713 }
714 }
715
716 70 ebuf->config_filename = saved_file;
717 70 ebuf->config_line = saved_line;
718
719
2/2
✓ Branch 0 (10→11) taken 63 times.
✓ Branch 1 (10→17) taken 7 times.
70 if (!r) {
720 return NULL;
721 }
722
723 63 const char *base = path_basename(config_filename);
724 63 Syntax *syntax = find_syntax(&e->syntaxes, base);
725
2/2
✓ Branch 0 (12→13) taken 2 times.
✓ Branch 1 (12→15) taken 61 times.
63 if (!syntax) {
726 2 error_msg (
727 &e->err,
728 "%s: no main syntax found (i.e. with name '%s')",
729 config_filename,
730 base
731 );
732 2 return NULL;
733 }
734
735
1/2
✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 61 times.
61 if (e->status != EDITOR_INITIALIZING) {
736 update_syntax_styles(syntax, &e->styles);
737 }
738
739 return syntax;
740 }
741
742 55 static Syntax *load_syntax_builtin(EditorState *e, const char *name, SyntaxLoadFlags flags)
743 {
744 55 const BuiltinConfig *cfg = get_builtin_config(name);
745
1/2
✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→6) not taken.
55 if (!cfg) {
746
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 55 times.
55 if (flags & SYN_MUST_EXIST) {
747 error_msg(&e->err, "no built-in config with name '%s'", name);
748 }
749 55 return NULL;
750 }
751 return load_syntax(e, cfg->text, name, flags | SYN_BUILTIN);
752 }
753
754 57 Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags)
755 {
756 57 char *alloc;
757 57 ssize_t size = read_file(filename, &alloc, 0);
758
2/2
✓ Branch 0 (3→4) taken 55 times.
✓ Branch 1 (3→10) taken 2 times.
57 if (size < 0) {
759 55 int err = errno;
760
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)) {
761 error_msg(&e->err, "Error reading %s: %s", filename, strerror(err));
762 errno = err;
763 }
764 55 return NULL;
765 }
766
767 2 StringView config = string_view(alloc, size);
768 2 Syntax *syntax = load_syntax(e, config, filename, flags);
769 2 free(alloc);
770
771
2/2
✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 1 times.
2 if (unlikely(!syntax)) {
772 1 errno = EINVAL;
773 }
774
775 return syntax;
776 }
777
778 55 Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype)
779 {
780
1/2
✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→8) not taken.
55 if (!is_valid_filetype_name(filetype)) {
781 return NULL;
782 }
783
784 55 const char *cfgdir = e->user_config_dir;
785 55 char filename[8192];
786 55 xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype);
787
788 55 Syntax *syn = load_syntax_file(e, filename, 0);
789
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) {
790 return syn;
791 }
792
793 // Skip past "%s/" (cfgdir) part of formatted string from above and
794 // try to load a built-in syntax named `syntax/<filetype>`
795 55 const char *builtin_name = filename + strlen(cfgdir) + STRLEN("/");
796 55 return load_syntax_builtin(e, builtin_name, 0);
797 }
798