dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 91.5% 344 / 0 / 376
Functions: 94.6% 35 / 0 / 37
Branches: 69.8% 155 / 8 / 230

src/syntax/state.c
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 3973 static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
26 {
27
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 3973 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
3973 return likely(syn->current_syntax) || error_msg(ebuf, "No syntax started");
28 }
29
30 2864 static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf)
31 {
32
1/2
✓ Branch 3 → 4 taken 2864 times.
✗ Branch 3 → 9 not taken.
2864 if (unlikely(!in_syntax(syn, ebuf))) {
33 return false;
34 }
35
1/4
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 8 taken 2864 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
2864 return likely(syn->current_state) || error_msg(ebuf, "No state started");
36 }
37
38 1264 static bool close_state(SyntaxLoader *syn, ErrorBuffer *ebuf)
39 {
40 1264 const State *state = syn->current_state;
41
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 1263 times.
1264 if (!state) {
42 return true;
43 }
44
45 1 syn->current_state = NULL;
46
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 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 2754 static State *find_or_add_state(const SyntaxLoader *syn, const char *name)
57 {
58 2754 State *state = find_state(syn->current_syntax, name);
59
2/2
✓ Branch 3 → 4 taken 939 times.
✓ Branch 3 → 9 taken 1815 times.
2754 if (state) {
60 return state;
61 }
62
63 939 state = xcalloc1(sizeof(*state));
64 939 state->name = xstrdup(name);
65 939 state->defined = false;
66 939 state->type = STATE_INVALID;
67
68
2/2
✓ Branch 6 → 7 taken 155 times.
✓ Branch 6 → 8 taken 784 times.
939 if (syn->current_syntax->states.count == 0) {
69 155 syn->current_syntax->start_state = state;
70 }
71
72 939 return hashmap_insert(&syn->current_syntax->states, state->name, state);
73 }
74
75 2493 static State *reference_state (
76 const SyntaxLoader *syn,
77 ErrorBuffer *ebuf,
78 const char *name
79 ) {
80
2/2
✓ Branch 2 → 3 taken 677 times.
✓ Branch 2 → 4 taken 1816 times.
2493 if (streq(name, "this")) {
81 677 return syn->current_state;
82 }
83
84 1816 State *state = find_or_add_state(syn, name);
85
4/4
✓ Branch 5 → 6 taken 1782 times.
✓ Branch 5 → 8 taken 34 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 1781 times.
1816 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 279 static bool in_subsyntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
98 {
99 279 bool ss = likely(is_subsyntax(syn->current_syntax));
100
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 279 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
279 return ss || error_msg(ebuf, "Destination state 'END' only allowed in a subsyntax");
101 }
102
103 194 static Syntax *must_find_subsyntax (
104 const HashMap *syntaxes,
105 ErrorBuffer *ebuf,
106 const char *name
107 ) {
108 194 Syntax *syntax = find_any_syntax(syntaxes, name);
109
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 194 times.
194 if (unlikely(!syntax)) {
110 error_msg(ebuf, "No such syntax '%s'", name);
111 return NULL;
112 }
113
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 194 times.
194 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 181 static bool subsyntax_call (
121 EditorState *e,
122 const char *name,
123 const char *ret,
124 State **dest
125 ) {
126 181 Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, name);
127
128 181 SyntaxMerge m = {
129 .subsyn = subsyn,
130 .return_state = NULL,
131 .delim = NULL,
132 .delim_len = 0,
133 };
134
135
2/2
✓ Branch 3 → 4 taken 19 times.
✓ Branch 3 → 6 taken 162 times.
181 if (streq(ret, "END")) {
136
1/2
✓ Branch 5 → 9 taken 19 times.
✗ Branch 5 → 12 not taken.
19 if (!in_subsyntax(&e->syn, &e->err)) {
137 return false;
138 }
139
1/2
✓ Branch 6 → 7 taken 162 times.
✗ Branch 6 → 12 not taken.
162 } else if (subsyn) {
140 162 m.return_state = reference_state(&e->syn, &e->err, ret);
141 }
142
143
1/2
✓ Branch 9 → 10 taken 181 times.
✗ Branch 9 → 12 not taken.
181 if (subsyn) {
144 181 *dest = merge_syntax(e->syn.current_syntax, &m, &e->styles);
145 181 return true;
146 }
147
148 return false;
149 }
150
151 2772 static bool destination_state(EditorState *e, const char *name, State **dest)
152 {
153 2772 const char *sep = strchr(name, ':');
154
2/2
✓ Branch 2 → 3 taken 181 times.
✓ Branch 2 → 6 taken 2591 times.
2772 if (sep) {
155 // subsyntax:returnstate
156 181 char *sub = xstrcut(name, sep - name);
157 181 bool success = subsyntax_call(e, sub, sep + 1, dest);
158 181 free(sub);
159 181 return success;
160 }
161
162
2/2
✓ Branch 6 → 7 taken 260 times.
✓ Branch 6 → 10 taken 2331 times.
2591 if (streq(name, "END")) {
163
1/2
✓ Branch 8 → 9 taken 260 times.
✗ Branch 8 → 12 not taken.
260 if (!in_subsyntax(&e->syn, &e->err)) {
164 return false;
165 }
166 260 *dest = NULL;
167 260 return true;
168 }
169
170 2331 *dest = reference_state(&e->syn, &e->err, name);
171 2331 return true;
172 }
173
174 2199 static void lint_emit_name (
175 ErrorBuffer *ebuf,
176 SyntaxLoadFlags flags,
177 const char *ename,
178 const State *dest
179 ) {
180 2199 if (
181
2/2
✓ Branch 2 → 3 taken 2150 times.
✓ Branch 2 → 7 taken 49 times.
2199 (flags & SYN_LINT)
182 2150 && ename
183
2/2
✓ Branch 3 → 4 taken 285 times.
✓ Branch 3 → 7 taken 1865 times.
2150 && dest
184
2/2
✓ Branch 4 → 5 taken 215 times.
✓ Branch 4 → 7 taken 70 times.
285 && dest->emit_name
185
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 214 times.
215 && 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 2199 }
194
195 1927 static Condition *add_condition (
196 EditorState *e,
197 ConditionType type,
198 const char *dest,
199 const char *emit
200 ) {
201 1927 BUG_ON(!dest && cond_type_has_destination(type));
202
1/2
✓ Branch 6 → 7 taken 1927 times.
✗ Branch 6 → 18 not taken.
1927 if (!in_state(&e->syn, &e->err)) {
203 return NULL;
204 }
205
206 1927 State *d = NULL;
207
3/4
✓ Branch 7 → 8 taken 1835 times.
✓ Branch 7 → 10 taken 92 times.
✓ Branch 9 → 10 taken 1835 times.
✗ Branch 9 → 18 not taken.
1927 if (dest && !destination_state(e, dest, &d)) {
208 return NULL;
209 }
210
211
2/2
✓ Branch 10 → 11 taken 559 times.
✓ Branch 10 → 12 taken 1368 times.
1927 emit = emit ? str_intern(emit) : NULL;
212
213 1927 if (
214 1927 type != COND_HEREDOCEND
215
2/2
✓ Branch 12 → 13 taken 1812 times.
✓ Branch 12 → 15 taken 115 times.
1927 && type != COND_INLIST
216
1/2
✓ Branch 13 → 14 taken 1812 times.
✗ Branch 13 → 15 not taken.
1812 && type != COND_INLIST_BUFFER
217 ) {
218 1812 lint_emit_name(&e->err, e->syn.flags, emit, d);
219 }
220
221 1927 Condition *c = xcalloc1(sizeof(*c));
222 1927 c->a.destination = d;
223 1927 c->a.emit_name = emit;
224 1927 c->type = type;
225 1927 ptr_array_append(&e->syn.current_state->conds, c);
226 1927 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 2 → 3 not taken.
✓ Branch 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 5 → 6 taken 18 times.
✗ Branch 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 1581 static bool cmd_char(EditorState *e, const CommandArgs *a)
254 {
255 1581 const char *chars = a->args[0];
256
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1581 times.
1581 if (unlikely(chars[0] == '\0')) {
257 return error_msg(&e->err, "char argument can't be empty");
258 }
259
260 1581 bool add_to_buffer = cmdargs_has_flag(a, 'b');
261 1581 bool invert = cmdargs_has_flag(a, 'n');
262 1581 ConditionType type;
263
2/2
✓ Branch 6 → 7 taken 1341 times.
✓ Branch 6 → 10 taken 240 times.
1581 if (add_to_buffer) {
264 type = COND_CHAR_BUFFER;
265
4/4
✓ Branch 7 → 8 taken 1310 times.
✓ Branch 7 → 9 taken 31 times.
✓ Branch 8 → 9 taken 537 times.
✓ Branch 8 → 10 taken 773 times.
1341 } else if (!invert && chars[1] == '\0') {
266 type = COND_CHAR1;
267 } else {
268 568 type = COND_CHAR;
269 }
270
271 1581 Condition *c = add_condition(e, type, a->args[1], a->args[2]);
272
1/2
✓ Branch 11 → 12 taken 1581 times.
✗ Branch 11 → 17 not taken.
1581 if (!c) {
273 return false;
274 }
275
276
2/2
✓ Branch 12 → 13 taken 773 times.
✓ Branch 12 → 14 taken 808 times.
1581 if (type == COND_CHAR1) {
277 773 c->u.ch = (unsigned char)chars[0];
278 } else {
279 808 bitset_add_char_range(c->u.bitset, chars);
280
2/2
✓ Branch 15 → 16 taken 44 times.
✓ Branch 15 → 17 taken 764 times.
808 if (invert) {
281 44 BITSET_INVERT(c->u.bitset);
282 }
283 }
284
285 return true;
286 }
287
288 69 static bool cmd_default(EditorState *e, const CommandArgs *a)
289 {
290 69 SyntaxLoader *syn = &e->syn;
291 69 ErrorBuffer *ebuf = &e->err;
292
2/4
✓ Branch 3 → 4 taken 69 times.
✗ Branch 3 → 14 not taken.
✓ Branch 5 → 6 taken 69 times.
✗ Branch 5 → 14 not taken.
69 if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) {
293 return false;
294 }
295
296 69 const char *value = str_intern(a->args[0]);
297 69 HashMap *map = &syn->current_syntax->default_styles;
298
2/2
✓ Branch 13 → 8 taken 81 times.
✓ Branch 13 → 14 taken 69 times.
150 for (size_t i = 1, n = a->nr_args; i < n; i++) {
299 81 const char *name = a->args[i];
300 81 const void *oldval = hashmap_insert_or_replace(map, xstrdup(name), (char*)value);
301
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 81 times.
81 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 387 static bool cmd_eat(EditorState *e, const CommandArgs *a)
311 {
312 387 SyntaxLoader *syn = &e->syn;
313
1/2
✓ Branch 3 → 4 taken 387 times.
✗ Branch 3 → 10 not taken.
387 if (!in_state(syn, &e->err)) {
314 return false;
315 }
316
317 387 const char *dest = a->args[0];
318 387 State *curstate = syn->current_state;
319
1/2
✓ Branch 5 → 6 taken 387 times.
✗ Branch 5 → 10 not taken.
387 if (!destination_state(e, dest, &curstate->default_action.destination)) {
320 return false;
321 }
322
323
2/2
✓ Branch 6 → 7 taken 91 times.
✓ Branch 6 → 8 taken 296 times.
387 const char *emit = a->args[1] ? str_intern(a->args[1]) : NULL;
324 387 lint_emit_name(&e->err, syn->flags, emit, curstate->default_action.destination);
325 387 curstate->default_action.emit_name = emit;
326 387 curstate->type = STATE_EAT;
327 387 syn->current_state = NULL;
328 387 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 3 → 4 taken 13 times.
✗ Branch 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 5 → 6 taken 13 times.
✗ Branch 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 7 → 8 taken 13 times.
✗ Branch 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 3 → 4 taken 10 times.
✗ Branch 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 3 → 4 taken 102 times.
✗ Branch 3 → 18 not taken.
✓ Branch 5 → 6 taken 102 times.
✗ Branch 5 → 18 not taken.
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 7 → 8 taken 8 times.
✓ Branch 7 → 11 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 11 → 12 not taken.
✓ Branch 11 → 13 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 list->strings = hashset_new(a->nr_args - 1, icase);
407
2/2
✓ Branch 17 → 15 taken 3666 times.
✓ Branch 17 → 18 taken 102 times.
3768 for (size_t i = 1, n = a->nr_args; i < n; i++) {
408 3666 const char *str = args[i];
409 3666 hashset_insert(&list->strings, 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 3 → 4 taken 105 times.
✗ Branch 3 → 5 not taken.
105 ConditionType type = cmdargs_has_flag(a, 'b') ? COND_INLIST_BUFFER : COND_INLIST;
419
2/2
✓ Branch 5 → 6 taken 27 times.
✓ Branch 5 → 7 taken 78 times.
132 Condition *c = add_condition(e, type, args[1], args[2] ? args[2] : name);
420
421
1/2
✓ Branch 8 → 9 taken 105 times.
✗ Branch 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 10 → 11 taken 95 times.
✓ Branch 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 537 static bool cmd_noeat(EditorState *e, const CommandArgs *a)
438 {
439 537 SyntaxLoader *syn = &e->syn;
440 537 State *dest;
441
2/4
✓ Branch 3 → 4 taken 537 times.
✗ Branch 3 → 12 not taken.
✓ Branch 5 → 6 taken 537 times.
✗ Branch 5 → 12 not taken.
537 if (unlikely(!in_state(syn, &e->err) || !destination_state(e, a->args[0], &dest))) {
442 return false;
443 }
444
445 537 State *curstate = syn->current_state;
446
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 536 times.
537 if (unlikely(dest == curstate)) {
447 1 return error_msg(&e->err, "using noeat to jump to same state causes infinite loop");
448 }
449
450 536 Action *defaction = &curstate->default_action;
451 536 bool bflag = cmdargs_has_flag(a, 'b');
452 536 defaction->destination = dest;
453 536 defaction->emit_name = NULL;
454
2/2
✓ Branch 9 → 10 taken 509 times.
✓ Branch 9 → 11 taken 27 times.
536 curstate->type = bflag ? STATE_NOEAT_BUFFER : STATE_NOEAT;
455 536 syn->current_state = NULL;
456 536 return true;
457 }
458
459 92 static bool cmd_recolor(EditorState *e, const CommandArgs *a)
460 {
461 // If length is not specified then buffered bytes will be recolored
462 92 ConditionType type = COND_RECOLOR_BUFFER;
463 92 size_t len = 0;
464
465 92 const char *len_str = a->args[1];
466
2/2
✓ Branch 2 → 3 taken 58 times.
✓ Branch 2 → 8 taken 34 times.
92 if (len_str) {
467 58 type = COND_RECOLOR;
468
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 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 6 → 7 not taken.
✓ Branch 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 92 Condition *c = add_condition(e, type, NULL, a->args[0]);
477
1/2
✓ Branch 9 → 10 taken 92 times.
✗ Branch 9 → 12 not taken.
92 if (!c) {
478 return false;
479 }
480
481
2/2
✓ Branch 10 → 11 taken 58 times.
✓ Branch 10 → 12 taken 34 times.
92 if (type == COND_RECOLOR) {
482 58 c->u.recolor_len = len;
483 }
484
485 return true;
486 }
487
488 18 static bool cmd_require(EditorState *e, const CommandArgs *a)
489 {
490 18 char buf[8192];
491 18 char *path;
492 18 size_t path_len;
493 18 HashSet *set;
494 18 SyntaxLoadFlags flags = SYN_MUST_EXIST;
495
496
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 18 times.
18 if (a->flags[0] == 'f') {
497 set = &e->required_syntax_files;
498 path = a->args[0];
499 path_len = strlen(path);
500 } else {
501 18 set = &e->required_syntax_builtins;
502 18 path_len = xsnprintf(buf, sizeof(buf), "syntax/inc/%s", a->args[0]);
503 18 path = buf;
504 18 flags |= SYN_BUILTIN;
505 }
506
507
2/2
✓ Branch 6 → 7 taken 5 times.
✓ Branch 6 → 11 taken 13 times.
18 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 SystemErrno err = read_syntax(e, path, flags);
515 5 syn->flags = save;
516
1/2
✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 11 not taken.
5 if (err) {
517 return false;
518 }
519
520 5 hashset_insert(set, path, path_len);
521 5 return true;
522 }
523
524 938 static bool cmd_state(EditorState *e, const CommandArgs *a)
525 {
526 938 SyntaxLoader *syn = &e->syn;
527 938 ErrorBuffer *ebuf = &e->err;
528
2/4
✓ Branch 3 → 4 taken 938 times.
✗ Branch 3 → 22 not taken.
✓ Branch 5 → 6 taken 938 times.
✗ Branch 5 → 22 not taken.
938 if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) {
529 return false;
530 }
531
532 938 const char *name = a->args[0];
533
2/4
✓ Branch 6 → 7 taken 938 times.
✗ Branch 6 → 8 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 938 times.
938 if (unlikely(streq(name, "END") || streq(name, "this"))) {
534 return error_msg(ebuf, "'%s' is reserved state name", name);
535 }
536
537 938 State *state = find_or_add_state(syn, name);
538
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 938 times.
938 if (unlikely(state->defined)) {
539 return error_msg(ebuf, "State '%s' already exists", name);
540 }
541
542 938 const char *emit_name = a->args[1];
543
5/6
✓ Branch 12 → 13 taken 920 times.
✓ Branch 12 → 18 taken 18 times.
✓ Branch 13 → 14 taken 302 times.
✓ Branch 13 → 15 taken 618 times.
✓ Branch 15 → 16 taken 618 times.
✗ Branch 15 → 17 not taken.
938 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 938 state->defined = true;
549
2/2
✓ Branch 18 → 19 taken 10 times.
✓ Branch 18 → 20 taken 8 times.
948 state->emit_name = str_intern(emit_name ? emit_name : name);
550 938 syn->current_state = state;
551 938 return true;
552 }
553
554 121 static bool cmd_str(EditorState *e, const CommandArgs *a)
555 {
556 121 const char *str = a->args[0];
557 121 size_t len = strlen(str);
558
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 121 times.
121 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 121 Condition *c;
566 121 size_t maxlen = ARRAYLEN(c->u.str.buf);
567
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 121 times.
121 if (unlikely(len > maxlen)) {
568 return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen);
569 }
570
571 121 ConditionType type;
572
2/2
✓ Branch 7 → 8 taken 114 times.
✓ Branch 7 → 10 taken 7 times.
121 if (cmdargs_has_flag(a, 'i')) {
573 type = COND_STR_ICASE;
574 } else {
575
2/2
✓ Branch 8 → 9 taken 37 times.
✓ Branch 8 → 10 taken 77 times.
114 type = (len == 2) ? COND_STR2 : COND_STR;
576 }
577
578 121 c = add_condition(e, type, a->args[1], a->args[2]);
579
1/2
✓ Branch 11 → 12 taken 121 times.
✗ Branch 11 → 13 not taken.
121 if (!c) {
580 return false;
581 }
582
583 121 memcpy(c->u.str.buf, str, len);
584 121 c->u.str.len = len;
585 121 return true;
586 }
587
588 155 static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes)
589 {
590 155 Syntax *syntax = syn->current_syntax;
591 155 BUG_ON(!syntax);
592
4/4
✓ Branch 5 → 6 taken 154 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 9 taken 149 times.
155 bool r = close_state(syn, ebuf) && finalize_syntax(syntaxes, syntax, ebuf);
593 6 if (!r) {
594 6 free_syntax(syntax);
595 }
596 155 syn->current_syntax = NULL;
597 155 return r;
598 }
599
600 156 static bool cmd_syntax(EditorState *e, const CommandArgs *a)
601 {
602 156 SyntaxLoader *syn = &e->syn;
603
3/4
✓ Branch 2 → 3 taken 85 times.
✓ Branch 2 → 5 taken 71 times.
✓ Branch 4 → 5 taken 85 times.
✗ Branch 4 → 11 not taken.
156 if (syn->current_syntax && !finish_syntax(syn, &e->err, &e->syntaxes)) {
604 return false;
605 }
606
607 156 Syntax *syntax = xcalloc1(sizeof(*syntax));
608 156 syntax->name = xstrdup(a->args[0]);
609
4/4
✓ Branch 7 → 8 taken 84 times.
✓ Branch 7 → 10 taken 72 times.
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 10 taken 77 times.
156 if (is_subsyntax(syntax) && !(syn->flags & SYN_WARN_ON_UNUSED_SUBSYN)) {
610 7 syntax->warned_unused_subsyntax = true;
611 }
612
613 156 syn->current_syntax = syntax;
614 156 syn->current_state = NULL;
615 156 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_CMDS_ARRAY(cmds);
641 24 }
642
643 4147 static const Command *find_syntax_command(const char *name)
644 {
645 4147 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 77 static CommandRunner cmdrunner_for_syntaxes(EditorState *e)
661 {
662 77 CommandRunner runner = cmdrunner(e, &syntax_commands);
663 77 runner.expand_variable = expand_syntax_var;
664 77 runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR;
665 77 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 SystemErrno read_syntax (
677 EditorState *e,
678 const char *filename,
679 SyntaxLoadFlags flags
680 ) {
681 5 CommandRunner runner = cmdrunner_for_syntaxes(e);
682 5 return read_config(&runner, filename, syn_flags_to_cfg_flags(flags));
683 }
684
685 72 Syntax *load_syntax (
686 EditorState *e,
687 StringView config_text,
688 const char *config_filename,
689 SyntaxLoadFlags flags
690 ) {
691 72 SyntaxLoader *syn = &e->syn;
692 72 *syn = (SyntaxLoader) {
693 .current_syntax = NULL,
694 .current_state = NULL,
695 72 .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN,
696 };
697
698 72 ErrorBuffer *ebuf = &e->err;
699 72 ConfigLocation save = ebuf->sourcepos;
700 72 CommandRunner runner = cmdrunner_for_syntaxes(e);
701
702 72 ebuf->sourcepos.filename = config_filename;
703 72 ebuf->sourcepos.line = 1;
704 72 bool r = exec_config(&runner, config_text);
705
706
2/2
✓ Branch 3 → 4 taken 71 times.
✓ Branch 3 → 10 taken 1 time.
72 if (syn->current_syntax) {
707
2/2
✓ Branch 4 → 5 taken 70 times.
✓ Branch 4 → 8 taken 1 time.
71 if (r) {
708 70 r = finish_syntax(syn, ebuf, &e->syntaxes);
709
2/2
✓ Branch 6 → 7 taken 64 times.
✓ Branch 6 → 10 taken 6 times.
70 if (r) {
710 64 find_unused_subsyntaxes(&e->syntaxes, ebuf);
711 }
712 } else {
713 1 free_syntax(syn->current_syntax);
714 1 syn->current_syntax = NULL;
715 }
716 }
717
718 72 ebuf->sourcepos = save;
719
720
2/2
✓ Branch 10 → 11 taken 65 times.
✓ Branch 10 → 17 taken 7 times.
72 if (!r) {
721 return NULL;
722 }
723
724 65 const char *base = path_basename(config_filename);
725 65 Syntax *syntax = find_syntax(&e->syntaxes, base);
726
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 15 taken 63 times.
65 if (!syntax) {
727 2 error_msg (
728 &e->err,
729 "%s: no main syntax found (i.e. with name '%s')",
730 config_filename,
731 base
732 );
733 2 return NULL;
734 }
735
736
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 63 times.
63 if (e->status != EDITOR_INITIALIZING) {
737 update_syntax_styles(syntax, &e->styles);
738 }
739
740 return syntax;
741 }
742
743 57 static Syntax *load_syntax_builtin(EditorState *e, const char *name, SyntaxLoadFlags flags)
744 {
745 57 const BuiltinConfig *cfg = get_builtin_config(name);
746
1/2
✓ Branch 2 → 3 taken 57 times.
✗ Branch 2 → 5 not taken.
57 if (!cfg) {
747
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 57 times.
57 if (flags & SYN_MUST_EXIST) {
748 error_msg(&e->err, "no built-in config with name '%s'", name);
749 }
750 return NULL;
751 }
752 return load_syntax(e, cfg->text, name, flags | SYN_BUILTIN);
753 }
754
755 116 Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags)
756 {
757 116 char *alloc;
758 116 ssize_t size = read_file(filename, &alloc, 0);
759
2/2
✓ Branch 3 → 4 taken 57 times.
✓ Branch 3 → 9 taken 59 times.
116 if (size < 0) {
760 57 int err = errno;
761
2/4
✓ Branch 4 → 5 taken 57 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 12 taken 57 times.
57 if (err != ENOENT || (flags & SYN_MUST_EXIST)) {
762 error_msg(&e->err, "Error reading %s: %s", filename, strerror(err));
763 errno = err;
764 }
765 return NULL;
766 }
767
768 59 StringView config = string_view(alloc, size);
769 59 Syntax *syntax = load_syntax(e, config, filename, flags);
770 59 free(alloc);
771
772
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 58 times.
59 if (unlikely(!syntax)) {
773 1 errno = EINVAL;
774 }
775
776 return syntax;
777 }
778
779 57 Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype)
780 {
781
1/2
✓ Branch 2 → 3 taken 57 times.
✗ Branch 2 → 8 not taken.
57 if (!is_valid_filetype_name(filetype)) {
782 return NULL;
783 }
784
785 57 const char *cfgdir = e->user_config_dir;
786 57 char filename[8192];
787 57 xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype);
788
789 57 Syntax *syn = load_syntax_file(e, filename, 0);
790
2/4
✓ Branch 5 → 6 taken 57 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 57 times.
✗ Branch 6 → 8 not taken.
57 if (syn || errno != ENOENT) {
791 return syn;
792 }
793
794 // Skip past "%s/" (cfgdir) part of formatted string from above and
795 // try to load a built-in syntax named `syntax/<filetype>`
796 57 const char *builtin_name = filename + strlen(cfgdir) + STRLEN("/");
797 57 return load_syntax_builtin(e, builtin_name, 0);
798 }
799