dte test coverage


Directory: ./
File: src/syntax/state.c
Date: 2025-05-08 15:05:54
Exec Total Coverage
Lines: 297 336 88.4%
Functions: 33 35 94.3%
Branches: 119 190 62.6%

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