dte test coverage


Directory: ./
File: src/syntax/state.c
Date: 2025-02-14 16:55:22
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 3786 static bool in_syntax(EditorState *e)
26 {
27
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 3786 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
3786 return likely(e->syn.current_syntax) || error_msg(&e->err, "No syntax started");
28 }
29
30 2734 static bool in_state(EditorState *e)
31 {
32
1/2
✓ Branch 0 (3→4) taken 2734 times.
✗ Branch 1 (3→9) not taken.
2734 if (unlikely(!in_syntax(e))) {
33 return false;
34 }
35
1/4
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→8) taken 2734 times.
✗ Branch 2 (6→7) not taken.
✗ Branch 3 (6→8) not taken.
2734 return likely(e->syn.current_state) || error_msg(&e->err, "No state started");
36 }
37
38 1185 static void close_state(EditorState *e)
39 {
40 1185 const State *state = e->syn.current_state;
41
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 1185 times.
1185 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 2645 static State *find_or_add_state(EditorState *e, const char *name)
56 {
57 2645 State *state = find_state(e->syn.current_syntax, name);
58
2/2
✓ Branch 0 (3→4) taken 890 times.
✓ Branch 1 (3→9) taken 1755 times.
2645 if (state) {
59 return state;
60 }
61
62 890 state = xnew0(State, 1);
63 890 state->name = xstrdup(name);
64 890 state->defined = false;
65 890 state->type = STATE_INVALID;
66
67
2/2
✓ Branch 0 (6→7) taken 133 times.
✓ Branch 1 (6→8) taken 757 times.
890 if (e->syn.current_syntax->states.count == 0) {
68 133 e->syn.current_syntax->start_state = state;
69 }
70
71 890 return hashmap_insert(&e->syn.current_syntax->states, state->name, state);
72 }
73
74 2378 static State *reference_state(EditorState *e, const char *name)
75 {
76
2/2
✓ Branch 0 (2→3) taken 623 times.
✓ Branch 1 (2→4) taken 1755 times.
2378 if (streq(name, "this")) {
77 623 return e->syn.current_state;
78 }
79
80 1755 State *state = find_or_add_state(e, name);
81
1/4
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→8) taken 1755 times.
✗ Branch 2 (6→7) not taken.
✗ Branch 3 (6→8) not taken.
1755 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 267 static bool in_subsyntax(EditorState *e)
94 {
95 267 bool ss = likely(is_subsyntax(e->syn.current_syntax));
96
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 267 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
267 return ss || error_msg(&e->err, "Destination state 'END' only allowed in a subsyntax");
97 }
98
99 182 static Syntax *must_find_subsyntax(EditorState *e, const char *name)
100 {
101 182 Syntax *syntax = find_any_syntax(&e->syntaxes, name);
102
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 182 times.
182 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 182 times.
182 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 169 static bool subsyntax_call(EditorState *e, const char *name, const char *ret, State **dest)
114 {
115 169 Syntax *subsyn = must_find_subsyntax(e, name);
116
117 169 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 152 times.
169 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 152 times.
✗ Branch 1 (6→12) not taken.
152 } else if (subsyn) {
129 152 m.return_state = reference_state(e, ret);
130 }
131
132
1/2
✓ Branch 0 (9→10) taken 169 times.
✗ Branch 1 (9→12) not taken.
169 if (subsyn) {
133 169 *dest = merge_syntax(e->syn.current_syntax, &m, &e->styles);
134 169 return true;
135 }
136
137 return false;
138 }
139
140 2645 static bool destination_state(EditorState *e, const char *name, State **dest)
141 {
142 2645 const char *sep = strchr(name, ':');
143
2/2
✓ Branch 0 (2→3) taken 169 times.
✓ Branch 1 (2→6) taken 2476 times.
2645 if (sep) {
144 // subsyntax:returnstate
145 169 char *sub = xstrcut(name, sep - name);
146 169 bool success = subsyntax_call(e, sub, sep + 1, dest);
147 169 free(sub);
148 169 return success;
149 }
150
151
2/2
✓ Branch 0 (6→7) taken 250 times.
✓ Branch 1 (6→10) taken 2226 times.
2476 if (streq(name, "END")) {
152
1/2
✓ Branch 0 (8→9) taken 250 times.
✗ Branch 1 (8→12) not taken.
250 if (!in_subsyntax(e)) {
153 return false;
154 }
155 250 *dest = NULL;
156 250 return true;
157 }
158
159 2226 *dest = reference_state(e, name);
160 2226 return true;
161 }
162
163 2088 static void lint_emit_name(EditorState *e, const char *ename, const State *dest)
164 {
165 2088 if (
166
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→7) taken 2088 times.
2088 (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 2088 }
179
180 1844 static Condition *add_condition (
181 EditorState *e,
182 ConditionType type,
183 const char *dest,
184 const char *emit
185 ) {
186 1844 BUG_ON(!dest && cond_type_has_destination(type));
187
1/2
✓ Branch 0 (6→7) taken 1844 times.
✗ Branch 1 (6→18) not taken.
1844 if (!in_state(e)) {
188 return NULL;
189 }
190
191 1844 State *d = NULL;
192
3/4
✓ Branch 0 (7→8) taken 1755 times.
✓ Branch 1 (7→10) taken 89 times.
✓ Branch 2 (9→10) taken 1755 times.
✗ Branch 3 (9→18) not taken.
1844 if (dest && !destination_state(e, dest, &d)) {
193 return NULL;
194 }
195
196
2/2
✓ Branch 0 (10→11) taken 531 times.
✓ Branch 1 (10→12) taken 1313 times.
1844 emit = emit ? str_intern(emit) : NULL;
197
198 1844 if (
199 1844 type != COND_HEREDOCEND
200
2/2
✓ Branch 0 (12→13) taken 1735 times.
✓ Branch 1 (12→15) taken 109 times.
1844 && type != COND_INLIST
201
1/2
✓ Branch 0 (13→14) taken 1735 times.
✗ Branch 1 (13→15) not taken.
1735 && type != COND_INLIST_BUFFER
202 ) {
203 1735 lint_emit_name(e, emit, d);
204 }
205
206 1844 Condition *c = xnew0(Condition, 1);
207 1844 c->a.destination = d;
208 1844 c->a.emit_name = emit;
209 1844 c->type = type;
210 1844 ptr_array_append(&e->syn.current_state->conds, c);
211 1844 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 1513 static bool cmd_char(EditorState *e, const CommandArgs *a)
239 {
240 1513 const char *chars = a->args[0];
241
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1513 times.
1513 if (unlikely(chars[0] == '\0')) {
242 return error_msg(&e->err, "char argument can't be empty");
243 }
244
245 1513 bool add_to_buffer = cmdargs_has_flag(a, 'b');
246 1513 bool invert = cmdargs_has_flag(a, 'n');
247 1513 ConditionType type;
248
2/2
✓ Branch 0 (6→7) taken 1288 times.
✓ Branch 1 (6→10) taken 225 times.
1513 if (add_to_buffer) {
249 type = COND_CHAR_BUFFER;
250
4/4
✓ Branch 0 (7→8) taken 1259 times.
✓ Branch 1 (7→9) taken 29 times.
✓ Branch 2 (8→9) taken 529 times.
✓ Branch 3 (8→10) taken 730 times.
1288 } else if (!invert && chars[1] == '\0') {
251 type = COND_CHAR1;
252 } else {
253 558 type = COND_CHAR;
254 }
255
256 1513 Condition *c = add_condition(e, type, a->args[1], a->args[2]);
257
1/2
✓ Branch 0 (11→12) taken 1513 times.
✗ Branch 1 (11→17) not taken.
1513 if (!c) {
258 return false;
259 }
260
261
2/2
✓ Branch 0 (12→13) taken 730 times.
✓ Branch 1 (12→14) taken 783 times.
1513 if (type == COND_CHAR1) {
262 730 c->u.ch = (unsigned char)chars[0];
263 } else {
264 783 bitset_add_char_range(c->u.bitset, chars);
265
2/2
✓ Branch 0 (15→16) taken 42 times.
✓ Branch 1 (15→17) taken 741 times.
783 if (invert) {
266 42 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 353 static bool cmd_eat(EditorState *e, const CommandArgs *a)
294 {
295
1/2
✓ Branch 0 (3→4) taken 353 times.
✗ Branch 1 (3→10) not taken.
353 if (!in_state(e)) {
296 return false;
297 }
298
299 353 const char *dest = a->args[0];
300
1/2
✓ Branch 0 (5→6) taken 353 times.
✗ Branch 1 (5→10) not taken.
353 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 86 times.
✓ Branch 1 (6→8) taken 267 times.
353 const char *emit = a->args[1] ? str_intern(a->args[1]) : NULL;
305 353 State *curstate = e->syn.current_state;
306 353 lint_emit_name(e, emit, curstate->default_action.destination);
307 353 curstate->default_action.emit_name = emit;
308 353 curstate->type = STATE_EAT;
309 353 e->syn.current_state = NULL;
310 353 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 97 static bool cmd_list(EditorState *e, const CommandArgs *a)
366 {
367 97 close_state(e);
368
1/2
✓ Branch 0 (4→5) taken 97 times.
✗ Branch 1 (4→17) not taken.
97 if (!in_syntax(e)) {
369 return false;
370 }
371
372 97 char **args = a->args;
373 97 const char *name = args[0];
374 97 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 90 times.
97 if (!list) {
376 7 list = xnew0(StringList, 1);
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 90 times.
90 } else if (unlikely(list->defined)) {
379 return error_msg(&e->err, "List '%s' already exists", name);
380 }
381 97 list->defined = true;
382
383 97 bool icase = a->flags[0] == 'i';
384 97 HashSet *set = &list->strings;
385 97 hashset_init(set, a->nr_args - 1, icase);
386
2/2
✓ Branch 0 (16→14) taken 3509 times.
✓ Branch 1 (16→17) taken 97 times.
3606 for (size_t i = 1, n = a->nr_args; i < n; i++) {
387 3509 const char *str = args[i];
388 3509 hashset_insert(set, str, strlen(str));
389 }
390 return true;
391 }
392
393 100 static bool cmd_inlist(EditorState *e, const CommandArgs *a)
394 {
395 100 char **args = a->args;
396 100 const char *name = args[0];
397
1/2
✓ Branch 0 (3→4) taken 100 times.
✗ Branch 1 (3→5) not taken.
100 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 73 times.
127 Condition *c = add_condition(e, type, args[1], args[2] ? args[2] : name);
399
400
1/2
✓ Branch 0 (8→9) taken 100 times.
✗ Branch 1 (8→15) not taken.
100 if (!c) {
401 return false;
402 }
403
404 100 StringList *list = find_string_list(e->syn.current_syntax, name);
405
2/2
✓ Branch 0 (10→11) taken 90 times.
✓ Branch 1 (10→14) taken 10 times.
100 if (unlikely(!list)) {
406 // Add undefined list
407 90 list = xnew0(StringList, 1);
408 90 hashmap_insert(&e->syn.current_syntax->string_lists, xstrdup(name), list);
409 }
410
411 100 list->used = true;
412 100 c->u.str_list = list;
413 100 return true;
414 }
415
416 524 static bool cmd_noeat(EditorState *e, const CommandArgs *a)
417 {
418 524 State *dest;
419
2/4
✓ Branch 0 (3→4) taken 524 times.
✗ Branch 1 (3→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 524 times.
524 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 524 times.
524 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 524 e->syn.current_state->default_action.destination = dest;
428 524 e->syn.current_state->default_action.emit_name = NULL;
429
2/2
✓ Branch 0 (9→10) taken 497 times.
✓ Branch 1 (9→11) taken 27 times.
524 e->syn.current_state->type = a->flags[0] == 'b' ? STATE_NOEAT_BUFFER : STATE_NOEAT;
430 524 e->syn.current_state = NULL;
431 524 return true;
432 }
433
434 89 static bool cmd_recolor(EditorState *e, const CommandArgs *a)
435 {
436 // If length is not specified then buffered bytes will be recolored
437 89 ConditionType type = COND_RECOLOR_BUFFER;
438 89 size_t len = 0;
439
440 89 const char *len_str = a->args[1];
441
2/2
✓ Branch 0 (2→3) taken 57 times.
✓ Branch 1 (2→8) taken 32 times.
89 if (len_str) {
442 57 type = COND_RECOLOR;
443
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 57 times.
57 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 57 times.
57 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 89 Condition *c = add_condition(e, type, NULL, a->args[0]);
452
1/2
✓ Branch 0 (9→10) taken 89 times.
✗ Branch 1 (9→12) not taken.
89 if (!c) {
453 return false;
454 }
455
456
2/2
✓ Branch 0 (10→11) taken 57 times.
✓ Branch 1 (10→12) taken 32 times.
89 if (type == COND_RECOLOR) {
457 57 c->u.recolor_len = len;
458 }
459
460 return true;
461 }
462
463 13 static bool cmd_require(EditorState *e, const CommandArgs *a)
464 {
465 13 char buf[8192];
466 13 char *path;
467 13 size_t path_len;
468 13 HashSet *set;
469 13 SyntaxLoadFlags flags = SYN_MUST_EXIST;
470
471
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 13 times.
13 if (a->flags[0] == 'f') {
472 set = &e->required_syntax_files;
473 path = a->args[0];
474 path_len = strlen(path);
475 } else {
476 13 set = &e->required_syntax_builtins;
477 13 path_len = xsnprintf(buf, sizeof(buf), "syntax/inc/%s", a->args[0]);
478 13 path = buf;
479 13 flags |= SYN_BUILTIN;
480 }
481
482
2/2
✓ Branch 0 (6→7) taken 4 times.
✓ Branch 1 (6→11) taken 9 times.
13 if (hashset_get(set, path, path_len)) {
483 return true;
484 }
485
486 4 const SyntaxLoadFlags save = e->syn.flags;
487 4 e->syn.flags &= ~SYN_WARN_ON_UNUSED_SUBSYN;
488 4 int r = read_syntax(e, path, flags);
489 4 e->syn.flags = save;
490
1/2
✓ Branch 0 (8→9) taken 4 times.
✗ Branch 1 (8→11) not taken.
4 if (r != 0) {
491 return false;
492 }
493
494 4 hashset_insert(set, path, path_len);
495 4 return true;
496 }
497
498 890 static bool cmd_state(EditorState *e, const CommandArgs *a)
499 {
500 890 close_state(e);
501
1/2
✓ Branch 0 (4→5) taken 890 times.
✗ Branch 1 (4→15) not taken.
890 if (!in_syntax(e)) {
502 return false;
503 }
504
505 890 const char *name = a->args[0];
506
2/4
✓ Branch 0 (5→6) taken 890 times.
✗ Branch 1 (5→7) not taken.
✗ Branch 2 (6→7) not taken.
✓ Branch 3 (6→8) taken 890 times.
890 if (unlikely(streq(name, "END") || streq(name, "this"))) {
507 return error_msg(&e->err, "'%s' is reserved state name", name);
508 }
509
510 890 State *state = find_or_add_state(e, name);
511
1/2
✗ Branch 0 (9→10) not taken.
✓ Branch 1 (9→11) taken 890 times.
890 if (unlikely(state->defined)) {
512 return error_msg(&e->err, "State '%s' already exists", name);
513 }
514
515 890 state->defined = true;
516
2/2
✓ Branch 0 (11→12) taken 613 times.
✓ Branch 1 (11→13) taken 277 times.
1503 state->emit_name = str_intern(a->args[1] ? a->args[1] : name);
517 890 e->syn.current_state = state;
518 890 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 133 static void finish_syntax(EditorState *e)
553 {
554 133 BUG_ON(!e->syn.current_syntax);
555 133 close_state(e);
556 133 finalize_syntax(&e->syntaxes, e->syn.current_syntax, &e->err, e->syn.saved_nr_errors);
557 133 e->syn.current_syntax = NULL;
558 133 }
559
560 133 static bool cmd_syntax(EditorState *e, const CommandArgs *a)
561 {
562
2/2
✓ Branch 0 (2→3) taken 79 times.
✓ Branch 1 (2→4) taken 54 times.
133 if (e->syn.current_syntax) {
563 79 finish_syntax(e);
564 }
565
566 133 Syntax *syntax = xnew0(Syntax, 1);
567 133 syntax->name = xstrdup(a->args[0]);
568
4/4
✓ Branch 0 (6→7) taken 79 times.
✓ Branch 1 (6→9) taken 54 times.
✓ Branch 2 (7→8) taken 6 times.
✓ Branch 3 (7→9) taken 73 times.
133 if (is_subsyntax(syntax) && !(e->syn.flags & SYN_WARN_ON_UNUSED_SUBSYN)) {
569 6 syntax->warned_unused_subsyntax = true;
570 }
571
572 133 e->syn.current_syntax = syntax;
573 133 e->syn.current_state = NULL;
574 133 e->syn.saved_nr_errors = e->err.nr_errors;
575 133 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 18 UNITTEST {
600 18 CHECK_BSEARCH_ARRAY(cmds, name, strcmp);
601 18 }
602
603 3932 static const Command *find_syntax_command(const char *name)
604 {
605 3932 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 163 static CommandRunner cmdrunner_for_syntaxes(EditorState *e)
621 {
622 163 CommandRunner runner = cmdrunner(e, &syntax_commands);
623 163 runner.expand_variable = expand_syntax_var;
624 163 return runner;
625 }
626
627 163 static ConfigFlags syn_flags_to_cfg_flags(SyntaxLoadFlags flags)
628 {
629 163 static_assert(SYN_MUST_EXIST == (SyntaxLoadFlags)CFG_MUST_EXIST);
630 163 static_assert(SYN_BUILTIN == (SyntaxLoadFlags)CFG_BUILTIN);
631 163 SyntaxLoadFlags mask = SYN_MUST_EXIST | SYN_BUILTIN;
632 163 return (ConfigFlags)(flags & mask);
633 }
634
635 4 static int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags)
636 {
637 4 CommandRunner runner = cmdrunner_for_syntaxes(e);
638 4 return read_config(&runner, filename, syn_flags_to_cfg_flags(flags));
639 }
640
641 159 Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags, int *err)
642 {
643 159 e->syn = (SyntaxLoadState) {
644 .current_syntax = NULL,
645 .current_state = NULL,
646 159 .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN,
647 .saved_nr_errors = 0,
648 };
649
650 159 const char *saved_file = e->err.config_filename;
651 159 const unsigned int saved_line = e->err.config_line;
652 159 CommandRunner runner = cmdrunner_for_syntaxes(e);
653 159 *err = do_read_config(&runner, filename, syn_flags_to_cfg_flags(flags));
654
655
4/4
✓ Branch 0 (3→4) taken 107 times.
✓ Branch 1 (3→7) taken 52 times.
✓ Branch 2 (4→5) taken 54 times.
✓ Branch 3 (4→7) taken 53 times.
159 if (!*err && e->syn.current_syntax) {
656 54 finish_syntax(e);
657 54 find_unused_subsyntaxes(&e->syntaxes, &e->err);
658 }
659
660 159 e->err.config_filename = saved_file;
661 159 e->err.config_line = saved_line;
662
663
2/2
✓ Branch 0 (7→8) taken 107 times.
✓ Branch 1 (7→13) taken 52 times.
159 if (*err) {
664 return NULL;
665 }
666
667 107 Syntax *syn = find_syntax(&e->syntaxes, path_basename(filename));
668
2/2
✓ Branch 0 (9→10) taken 53 times.
✓ Branch 1 (9→11) taken 54 times.
107 if (!syn) {
669 53 *err = EINVAL;
670 53 return NULL;
671 }
672
673
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 54 times.
54 if (e->status != EDITOR_INITIALIZING) {
674 update_syntax_styles(syn, &e->styles);
675 }
676
677 return syn;
678 }
679
680 52 Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype)
681 {
682
1/2
✓ Branch 0 (2→3) taken 52 times.
✗ Branch 1 (2→9) not taken.
52 if (!is_valid_filetype_name(filetype)) {
683 return NULL;
684 }
685
686 52 const char *cfgdir = e->user_config_dir;
687 52 char filename[8192];
688 52 int err;
689
690 52 xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype);
691 52 Syntax *syn = load_syntax_file(e, filename, 0, &err);
692
2/4
✓ Branch 0 (5→6) taken 52 times.
✗ Branch 1 (5→9) not taken.
✓ Branch 2 (6→7) taken 52 times.
✗ Branch 3 (6→9) not taken.
52 if (syn || err != ENOENT) {
693 return syn;
694 }
695
696 52 xsnprintf(filename, sizeof filename, "syntax/%s", filetype);
697 52 return load_syntax_file(e, filename, SYN_BUILTIN, &err);
698 }
699