dte test coverage


Directory: ./
File: src/syntax/state.c
Date: 2025-09-07 23:01:39
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/errorcode.h"
16 #include "util/hashset.h"
17 #include "util/intern.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 3900 static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
26 {
27
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 3900 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
3900 return likely(syn->current_syntax) || error_msg(ebuf, "No syntax started");
28 }
29
30 2809 static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf)
31 {
32
1/2
✓ Branch 0 (3→4) taken 2809 times.
✗ Branch 1 (3→9) not taken.
2809 if (unlikely(!in_syntax(syn, ebuf))) {
33 return false;
34 }
35
1/4
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→8) taken 2809 times.
✗ Branch 2 (6→7) not taken.
✗ Branch 3 (6→8) not taken.
2809 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 2713 static State *find_or_add_state(const SyntaxLoader *syn, const char *name)
57 {
58 2713 State *state = find_state(syn->current_syntax, name);
59
2/2
✓ Branch 0 (3→4) taken 924 times.
✓ Branch 1 (3→9) taken 1789 times.
2713 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 2447 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 1790 times.
2447 if (streq(name, "this")) {
81 657 return syn->current_state;
82 }
83
84 1790 State *state = find_or_add_state(syn, name);
85
4/4
✓ Branch 0 (5→6) taken 4 times.
✓ Branch 1 (5→8) taken 1786 times.
✓ Branch 2 (6→7) taken 1 times.
✓ Branch 3 (6→8) taken 3 times.
1790 if (unlikely((syn->flags & SYN_LINT) && state == syn->current_state)) {
86 1 error_msg ( // Soft error
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 2719 static bool destination_state(EditorState *e, const char *name, State **dest)
152 {
153 2719 const char *sep = strchr(name, ':');
154
2/2
✓ Branch 0 (2→3) taken 175 times.
✓ Branch 1 (2→6) taken 2544 times.
2719 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 2289 times.
2544 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 2289 *dest = reference_state(&e->syn, &e->err, name);
171 2289 return true;
172 }
173
174 2149 static void lint_emit_name (
175 ErrorBuffer *ebuf,
176 SyntaxLoadFlags flags,
177 const char *ename,
178 const State *dest
179 ) {
180 2149 if (
181
2/2
✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→7) taken 2135 times.
2149 (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 ( // Soft error
188 ebuf,
189 "emit-name '%s' not needed (destination state uses same emit-name)",
190 ename
191 );
192 }
193 2149 }
194
195 1887 static Condition *add_condition (
196 EditorState *e,
197 ConditionType type,
198 const char *dest,
199 const char *emit
200 ) {
201 1887 BUG_ON(!dest && cond_type_has_destination(type));
202
1/2
✓ Branch 0 (6→7) taken 1887 times.
✗ Branch 1 (6→18) not taken.
1887 if (!in_state(&e->syn, &e->err)) {
203 return NULL;
204 }
205
206 1887 State *d = NULL;
207
3/4
✓ Branch 0 (7→8) taken 1797 times.
✓ Branch 1 (7→10) taken 90 times.
✓ Branch 2 (9→10) taken 1797 times.
✗ Branch 3 (9→18) not taken.
1887 if (dest && !destination_state(e, dest, &d)) {
208 return NULL;
209 }
210
211
2/2
✓ Branch 0 (10→11) taken 547 times.
✓ Branch 1 (10→12) taken 1340 times.
1887 emit = emit ? str_intern(emit) : NULL;
212
213 1887 if (
214 1887 type != COND_HEREDOCEND
215
2/2
✓ Branch 0 (12→13) taken 1772 times.
✓ Branch 1 (12→15) taken 115 times.
1887 && type != COND_INLIST
216
1/2
✓ Branch 0 (13→14) taken 1772 times.
✗ Branch 1 (13→15) not taken.
1772 && type != COND_INLIST_BUFFER
217 ) {
218 1772 lint_emit_name(&e->err, e->syn.flags, emit, d);
219 }
220
221 1887 Condition *c = xcalloc1(sizeof(*c));
222 1887 c->a.destination = d;
223 1887 c->a.emit_name = emit;
224 1887 c->type = type;
225 1887 ptr_array_append(&e->syn.current_state->conds, c);
226 1887 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 1546 static bool cmd_char(EditorState *e, const CommandArgs *a)
254 {
255 1546 const char *chars = a->args[0];
256
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1546 times.
1546 if (unlikely(chars[0] == '\0')) {
257 return error_msg(&e->err, "char argument can't be empty");
258 }
259
260 1546 bool add_to_buffer = cmdargs_has_flag(a, 'b');
261 1546 bool invert = cmdargs_has_flag(a, 'n');
262 1546 ConditionType type;
263
2/2
✓ Branch 0 (6→7) taken 1311 times.
✓ Branch 1 (6→10) taken 235 times.
1546 if (add_to_buffer) {
264 type = COND_CHAR_BUFFER;
265
4/4
✓ Branch 0 (7→8) taken 1280 times.
✓ Branch 1 (7→9) taken 31 times.
✓ Branch 2 (8→9) taken 533 times.
✓ Branch 3 (8→10) taken 747 times.
1311 } else if (!invert && chars[1] == '\0') {
266 type = COND_CHAR1;
267 } else {
268 564 type = COND_CHAR;
269 }
270
271 1546 Condition *c = add_condition(e, type, a->args[1], a->args[2]);
272
1/2
✓ Branch 0 (11→12) taken 1546 times.
✗ Branch 1 (11→17) not taken.
1546 if (!c) {
273 return false;
274 }
275
276
2/2
✓ Branch 0 (12→13) taken 747 times.
✓ Branch 1 (12→14) taken 799 times.
1546 if (type == COND_CHAR1) {
277 747 c->u.ch = (unsigned char)chars[0];
278 } else {
279 799 bitset_add_char_range(c->u.bitset, chars);
280
2/2
✓ Branch 0 (15→16) taken 44 times.
✓ Branch 1 (15→17) taken 755 times.
799 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 // Soft error
303 error_msg(ebuf, "'%s' argument specified multiple times", name);
304 }
305 }
306
307 return true;
308 }
309
310 377 static bool cmd_eat(EditorState *e, const CommandArgs *a)
311 {
312 377 SyntaxLoader *syn = &e->syn;
313
1/2
✓ Branch 0 (3→4) taken 377 times.
✗ Branch 1 (3→10) not taken.
377 if (!in_state(syn, &e->err)) {
314 return false;
315 }
316
317 377 const char *dest = a->args[0];
318 377 State *curstate = syn->current_state;
319
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)) {
320 return false;
321 }
322
323
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;
324 377 lint_emit_name(&e->err, syn->flags, emit, curstate->default_action.destination);
325 377 curstate->default_action.emit_name = emit;
326 377 curstate->type = STATE_EAT;
327 377 syn->current_state = NULL;
328 377 return true;
329 }
330
331 13 static bool cmd_heredocbegin(EditorState *e, const CommandArgs *a)
332 {
333 13 SyntaxLoader *syn = &e->syn;
334
1/2
✓ Branch 0 (3→4) taken 13 times.
✗ Branch 1 (3→9) not taken.
13 if (!in_state(syn, &e->err)) {
335 return false;
336 }
337
338 13 Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, a->args[0]);
339
1/2
✓ Branch 0 (5→6) taken 13 times.
✗ Branch 1 (5→9) not taken.
13 if (!subsyn) {
340 return false;
341 }
342
343 // default_action.destination is used as the return state
344 13 const char *ret = a->args[1];
345
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)) {
346 return false;
347 }
348
349 13 syn->current_state->default_action.emit_name = NULL;
350 13 syn->current_state->type = STATE_HEREDOCBEGIN;
351 13 syn->current_state->heredoc.subsyntax = subsyn;
352 13 syn->current_state = NULL;
353
354 // Normally merge() marks subsyntax used but in case of heredocs merge()
355 // is not called when syntax file is loaded
356 13 subsyn->used = true;
357 13 return true;
358 }
359
360 10 static bool cmd_heredocend(EditorState *e, const CommandArgs *a)
361 {
362 10 Condition *c = add_condition(e, COND_HEREDOCEND, a->args[0], a->args[1]);
363
1/2
✓ Branch 0 (3→4) taken 10 times.
✗ Branch 1 (3→7) not taken.
10 if (unlikely(!c)) {
364 return false;
365 }
366
367 10 Syntax *current_syntax = e->syn.current_syntax;
368 10 BUG_ON(!current_syntax);
369 10 current_syntax->heredoc = true;
370 10 return true;
371 }
372
373 // Forward declaration, used in cmd_include() and cmd_require()
374 static SystemErrno read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags);
375
376 static bool cmd_include(EditorState *e, const CommandArgs *a)
377 {
378 SyntaxLoadFlags flags = SYN_MUST_EXIST;
379 if (a->flags[0] == 'b') {
380 flags |= SYN_BUILTIN;
381 }
382 SystemErrno err = read_syntax(e, a->args[0], flags);
383 return !err;
384 }
385
386 102 static bool cmd_list(EditorState *e, const CommandArgs *a)
387 {
388 102 SyntaxLoader *syn = &e->syn;
389 102 ErrorBuffer *ebuf = &e->err;
390
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)) {
391 return false;
392 }
393
394 102 char **args = a->args;
395 102 const char *name = args[0];
396 102 StringList *list = find_string_list(syn->current_syntax, name);
397
2/2
✓ Branch 0 (8→9) taken 8 times.
✓ Branch 1 (8→12) taken 94 times.
102 if (!list) {
398 8 list = xcalloc1(sizeof(*list));
399 8 hashmap_insert(&syn->current_syntax->string_lists, xstrdup(name), list);
400
1/2
✗ Branch 0 (12→13) not taken.
✓ Branch 1 (12→14) taken 94 times.
94 } else if (unlikely(list->defined)) {
401 return error_msg(ebuf, "List '%s' already exists", name);
402 }
403 102 list->defined = true;
404
405 102 bool icase = a->flags[0] == 'i';
406 102 HashSet *set = &list->strings;
407 102 hashset_init(set, a->nr_args - 1, icase);
408
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++) {
409 3640 const char *str = args[i];
410 3640 hashset_insert(set, str, strlen(str));
411 }
412 return true;
413 }
414
415 105 static bool cmd_inlist(EditorState *e, const CommandArgs *a)
416 {
417 105 char **args = a->args;
418 105 const char *name = args[0];
419
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;
420
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);
421
422
1/2
✓ Branch 0 (8→9) taken 105 times.
✗ Branch 1 (8→15) not taken.
105 if (!c) {
423 return false;
424 }
425
426 105 StringList *list = find_string_list(e->syn.current_syntax, name);
427
2/2
✓ Branch 0 (10→11) taken 95 times.
✓ Branch 1 (10→14) taken 10 times.
105 if (unlikely(!list)) {
428 // Add undefined list
429 95 list = xcalloc1(sizeof(*list));
430 95 hashmap_insert(&e->syn.current_syntax->string_lists, xstrdup(name), list);
431 }
432
433 105 list->used = true;
434 105 c->u.str_list = list;
435 105 return true;
436 }
437
438 532 static bool cmd_noeat(EditorState *e, const CommandArgs *a)
439 {
440 532 SyntaxLoader *syn = &e->syn;
441 532 State *dest;
442
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))) {
443 return false;
444 }
445
446 532 State *curstate = syn->current_state;
447
2/2
✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 531 times.
532 if (unlikely(dest == curstate)) {
448 1 return error_msg(&e->err, "using noeat to jump to same state causes infinite loop");
449 }
450
451 531 Action *defaction = &curstate->default_action;
452 531 bool bflag = cmdargs_has_flag(a, 'b');
453 531 defaction->destination = dest;
454 531 defaction->emit_name = NULL;
455
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;
456 531 syn->current_state = NULL;
457 531 return true;
458 }
459
460 90 static bool cmd_recolor(EditorState *e, const CommandArgs *a)
461 {
462 // If length is not specified then buffered bytes will be recolored
463 90 ConditionType type = COND_RECOLOR_BUFFER;
464 90 size_t len = 0;
465
466 90 const char *len_str = a->args[1];
467
2/2
✓ Branch 0 (2→3) taken 58 times.
✓ Branch 1 (2→8) taken 32 times.
90 if (len_str) {
468 58 type = COND_RECOLOR;
469
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 58 times.
58 if (unlikely(!str_to_size(len_str, &len))) {
470 return error_msg(&e->err, "invalid number: '%s'", len_str);
471 }
472
1/2
✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→8) taken 58 times.
58 if (unlikely(len < 1 || len > 2500)) {
473 return error_msg(&e->err, "number of bytes must be between 1-2500 (got %zu)", len);
474 }
475 }
476
477 90 Condition *c = add_condition(e, type, NULL, a->args[0]);
478
1/2
✓ Branch 0 (9→10) taken 90 times.
✗ Branch 1 (9→12) not taken.
90 if (!c) {
479 return false;
480 }
481
482
2/2
✓ Branch 0 (10→11) taken 58 times.
✓ Branch 1 (10→12) taken 32 times.
90 if (type == COND_RECOLOR) {
483 58 c->u.recolor_len = len;
484 }
485
486 return true;
487 }
488
489 17 static bool cmd_require(EditorState *e, const CommandArgs *a)
490 {
491 17 char buf[8192];
492 17 char *path;
493 17 size_t path_len;
494 17 HashSet *set;
495 17 SyntaxLoadFlags flags = SYN_MUST_EXIST;
496
497
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 17 times.
17 if (a->flags[0] == 'f') {
498 set = &e->required_syntax_files;
499 path = a->args[0];
500 path_len = strlen(path);
501 } else {
502 17 set = &e->required_syntax_builtins;
503 17 path_len = xsnprintf(buf, sizeof(buf), "syntax/inc/%s", a->args[0]);
504 17 path = buf;
505 17 flags |= SYN_BUILTIN;
506 }
507
508
2/2
✓ Branch 0 (6→7) taken 5 times.
✓ Branch 1 (6→11) taken 12 times.
17 if (hashset_get(set, path, path_len)) {
509 return true;
510 }
511
512 5 SyntaxLoader *syn = &e->syn;
513 5 const SyntaxLoadFlags save = syn->flags;
514 5 syn->flags &= ~SYN_WARN_ON_UNUSED_SUBSYN;
515 5 SystemErrno err = read_syntax(e, path, flags);
516 5 syn->flags = save;
517
1/2
✓ Branch 0 (8→9) taken 5 times.
✗ Branch 1 (8→11) not taken.
5 if (err) {
518 return false;
519 }
520
521 5 hashset_insert(set, path, path_len);
522 5 return true;
523 }
524
525 923 static bool cmd_state(EditorState *e, const CommandArgs *a)
526 {
527 923 SyntaxLoader *syn = &e->syn;
528 923 ErrorBuffer *ebuf = &e->err;
529
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)) {
530 return false;
531 }
532
533 923 const char *name = a->args[0];
534
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"))) {
535 return error_msg(ebuf, "'%s' is reserved state name", name);
536 }
537
538 923 State *state = find_or_add_state(syn, name);
539
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 923 times.
923 if (unlikely(state->defined)) {
540 return error_msg(ebuf, "State '%s' already exists", name);
541 }
542
543 923 const char *emit_name = a->args[1];
544
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)) {
545 // Soft error
546 error_msg(ebuf, "Redundant emit-name '%s'", emit_name);
547 }
548
549 923 state->defined = true;
550
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);
551 923 syn->current_state = state;
552 923 return true;
553 }
554
555 118 static bool cmd_str(EditorState *e, const CommandArgs *a)
556 {
557 118 const char *str = a->args[0];
558 118 size_t len = strlen(str);
559
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 118 times.
118 if (unlikely(len < 2)) {
560 error_msg ( // Soft error
561 &e->err,
562 "string should be at least 2 bytes; use 'char' for single bytes"
563 );
564 }
565
566 118 Condition *c;
567 118 size_t maxlen = ARRAYLEN(c->u.str.buf);
568
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 118 times.
118 if (unlikely(len > maxlen)) {
569 return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen);
570 }
571
572 118 ConditionType type;
573
2/2
✓ Branch 0 (7→8) taken 111 times.
✓ Branch 1 (7→10) taken 7 times.
118 if (cmdargs_has_flag(a, 'i')) {
574 type = COND_STR_ICASE;
575 } else {
576
2/2
✓ Branch 0 (8→9) taken 36 times.
✓ Branch 1 (8→10) taken 75 times.
111 type = (len == 2) ? COND_STR2 : COND_STR;
577 }
578
579 118 c = add_condition(e, type, a->args[1], a->args[2]);
580
1/2
✓ Branch 0 (11→12) taken 118 times.
✗ Branch 1 (11→13) not taken.
118 if (!c) {
581 return false;
582 }
583
584 118 memcpy(c->u.str.buf, str, len);
585 118 c->u.str.len = len;
586 118 return true;
587 }
588
589 151 static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes)
590 {
591 151 Syntax *syntax = syn->current_syntax;
592 151 BUG_ON(!syntax);
593
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);
594 6 if (!r) {
595 6 free_syntax(syntax);
596 }
597 151 syn->current_syntax = NULL;
598 151 return r;
599 }
600
601 152 static bool cmd_syntax(EditorState *e, const CommandArgs *a)
602 {
603 152 SyntaxLoader *syn = &e->syn;
604
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)) {
605 return false;
606 }
607
608 152 Syntax *syntax = xcalloc1(sizeof(*syntax));
609 152 syntax->name = xstrdup(a->args[0]);
610
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)) {
611 7 syntax->warned_unused_subsyntax = true;
612 }
613
614 152 syn->current_syntax = syntax;
615 152 syn->current_state = NULL;
616 152 return true;
617 }
618
619 #define CMD(name, flags, min, max, func) \
620 {name, flags, CMDOPT_ALLOW_IN_RC, min, max, func}
621
622 static const Command cmds[] = {
623 CMD("bufis", "i", 2, 3, cmd_bufis),
624 CMD("char", "bn", 2, 3, cmd_char),
625 CMD("default", "", 2, -1, cmd_default),
626 CMD("eat", "", 1, 2, cmd_eat),
627 CMD("heredocbegin", "", 2, 2, cmd_heredocbegin),
628 CMD("heredocend", "", 1, 2, cmd_heredocend),
629 CMD("include", "b", 1, 1, cmd_include),
630 CMD("inlist", "b", 2, 3, cmd_inlist),
631 CMD("list", "i", 2, -1, cmd_list),
632 CMD("noeat", "b", 1, 1, cmd_noeat),
633 CMD("recolor", "", 1, 2, cmd_recolor),
634 CMD("require", "f", 1, 1, cmd_require),
635 CMD("state", "", 1, 2, cmd_state),
636 CMD("str", "i", 2, 3, cmd_str),
637 CMD("syntax", "", 1, 1, cmd_syntax),
638 };
639
640 24 UNITTEST {
641 24 CHECK_BSEARCH_ARRAY(cmds, name);
642 24 }
643
644 4069 static const Command *find_syntax_command(const char *name)
645 {
646 4069 return BSEARCH(name, cmds, command_cmp);
647 }
648
649 static char *expand_syntax_var(const EditorState *e, const char *name)
650 {
651 if (streq(name, "DTE_HOME")) {
652 return xstrdup(e->user_config_dir);
653 }
654 return NULL;
655 }
656
657 static const CommandSet syntax_commands = {
658 .lookup = find_syntax_command,
659 };
660
661 75 static CommandRunner cmdrunner_for_syntaxes(EditorState *e)
662 {
663 75 CommandRunner runner = cmdrunner(e, &syntax_commands);
664 75 runner.expand_variable = expand_syntax_var;
665 75 runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR;
666 75 return runner;
667 }
668
669 5 static ConfigFlags syn_flags_to_cfg_flags(SyntaxLoadFlags flags)
670 {
671 5 static_assert(SYN_MUST_EXIST == (SyntaxLoadFlags)CFG_MUST_EXIST);
672 5 static_assert(SYN_BUILTIN == (SyntaxLoadFlags)CFG_BUILTIN);
673 5 SyntaxLoadFlags mask = SYN_MUST_EXIST | SYN_BUILTIN;
674 5 return (ConfigFlags)(flags & mask);
675 }
676
677 5 static SystemErrno read_syntax (
678 EditorState *e,
679 const char *filename,
680 SyntaxLoadFlags flags
681 ) {
682 5 CommandRunner runner = cmdrunner_for_syntaxes(e);
683 5 return read_config(&runner, filename, syn_flags_to_cfg_flags(flags));
684 }
685
686 70 Syntax *load_syntax (
687 EditorState *e,
688 StringView config_text,
689 const char *config_filename,
690 SyntaxLoadFlags flags
691 ) {
692 70 SyntaxLoader *syn = &e->syn;
693 70 *syn = (SyntaxLoader) {
694 .current_syntax = NULL,
695 .current_state = NULL,
696 70 .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN,
697 };
698
699 70 ErrorBuffer *ebuf = &e->err;
700 70 const char *saved_file = ebuf->config_filename;
701 70 const unsigned int saved_line = ebuf->config_line;
702 70 CommandRunner runner = cmdrunner_for_syntaxes(e);
703
704 70 ebuf->config_filename = config_filename;
705 70 ebuf->config_line = 1;
706 70 bool r = exec_config(&runner, config_text);
707
708
2/2
✓ Branch 0 (3→4) taken 69 times.
✓ Branch 1 (3→10) taken 1 times.
70 if (syn->current_syntax) {
709
2/2
✓ Branch 0 (4→5) taken 68 times.
✓ Branch 1 (4→8) taken 1 times.
69 if (r) {
710 68 r = finish_syntax(syn, ebuf, &e->syntaxes);
711
2/2
✓ Branch 0 (6→7) taken 62 times.
✓ Branch 1 (6→10) taken 6 times.
68 if (r) {
712 62 find_unused_subsyntaxes(&e->syntaxes, ebuf);
713 }
714 } else {
715 1 free_syntax(syn->current_syntax);
716 1 syn->current_syntax = NULL;
717 }
718 }
719
720 70 ebuf->config_filename = saved_file;
721 70 ebuf->config_line = saved_line;
722
723
2/2
✓ Branch 0 (10→11) taken 63 times.
✓ Branch 1 (10→17) taken 7 times.
70 if (!r) {
724 return NULL;
725 }
726
727 63 const char *base = path_basename(config_filename);
728 63 Syntax *syntax = find_syntax(&e->syntaxes, base);
729
2/2
✓ Branch 0 (12→13) taken 2 times.
✓ Branch 1 (12→15) taken 61 times.
63 if (!syntax) {
730 2 error_msg (
731 &e->err,
732 "%s: no main syntax found (i.e. with name '%s')",
733 config_filename,
734 base
735 );
736 2 return NULL;
737 }
738
739
1/2
✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 61 times.
61 if (e->status != EDITOR_INITIALIZING) {
740 update_syntax_styles(syntax, &e->styles);
741 }
742
743 return syntax;
744 }
745
746 55 static Syntax *load_syntax_builtin(EditorState *e, const char *name, SyntaxLoadFlags flags)
747 {
748 55 const BuiltinConfig *cfg = get_builtin_config(name);
749
1/2
✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→6) not taken.
55 if (!cfg) {
750
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 55 times.
55 if (flags & SYN_MUST_EXIST) {
751 error_msg(&e->err, "no built-in config with name '%s'", name);
752 }
753 55 return NULL;
754 }
755 return load_syntax(e, cfg->text, name, flags | SYN_BUILTIN);
756 }
757
758 57 Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags)
759 {
760 57 char *alloc;
761 57 ssize_t size = read_file(filename, &alloc, 0);
762
2/2
✓ Branch 0 (3→4) taken 55 times.
✓ Branch 1 (3→10) taken 2 times.
57 if (size < 0) {
763 55 int err = errno;
764
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)) {
765 error_msg(&e->err, "Error reading %s: %s", filename, strerror(err));
766 errno = err;
767 }
768 55 return NULL;
769 }
770
771 2 StringView config = string_view(alloc, size);
772 2 Syntax *syntax = load_syntax(e, config, filename, flags);
773 2 free(alloc);
774
775
2/2
✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 1 times.
2 if (unlikely(!syntax)) {
776 1 errno = EINVAL;
777 }
778
779 return syntax;
780 }
781
782 55 Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype)
783 {
784
1/2
✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→8) not taken.
55 if (!is_valid_filetype_name(filetype)) {
785 return NULL;
786 }
787
788 55 const char *cfgdir = e->user_config_dir;
789 55 char filename[8192];
790 55 xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype);
791
792 55 Syntax *syn = load_syntax_file(e, filename, 0);
793
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) {
794 return syn;
795 }
796
797 // Skip past "%s/" (cfgdir) part of formatted string from above and
798 // try to load a built-in syntax named `syntax/<filetype>`
799 55 const char *builtin_name = filename + strlen(cfgdir) + STRLEN("/");
800 55 return load_syntax_builtin(e, builtin_name, 0);
801 }
802