dte test coverage


Directory: ./
File: src/syntax/state.c
Date: 2025-12-03 13:13:24
Coverage Exec Excl Total
Lines: 90.6% 349 0 385
Functions: 94.6% 35 0 37
Branches: 68.0% 151 0 222

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 3934 static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf)
26 {
27
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 3934 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
3934 return likely(syn->current_syntax) || error_msg(ebuf, "No syntax started");
28 }
29
30 2833 static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf)
31 {
32
1/2
✓ Branch 3 → 4 taken 2833 times.
✗ Branch 3 → 9 not taken.
2833 if (unlikely(!in_syntax(syn, ebuf))) {
33 return false;
34 }
35
1/4
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 8 taken 2833 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
2833 return likely(syn->current_state) || error_msg(ebuf, "No state started");
36 }
37
38 1253 static bool close_state(SyntaxLoader *syn, ErrorBuffer *ebuf)
39 {
40 1253 const State *state = syn->current_state;
41
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 1252 times.
1253 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 2735 static State *find_or_add_state(const SyntaxLoader *syn, const char *name)
57 {
58 2735 State *state = find_state(syn->current_syntax, name);
59
2/2
✓ Branch 3 → 4 taken 931 times.
✓ Branch 3 → 9 taken 1804 times.
2735 if (state) {
60 return state;
61 }
62
63 931 state = xcalloc1(sizeof(*state));
64 931 state->name = xstrdup(name);
65 931 state->defined = false;
66 931 state->type = STATE_INVALID;
67
68
2/2
✓ Branch 6 → 7 taken 152 times.
✓ Branch 6 → 8 taken 779 times.
931 if (syn->current_syntax->states.count == 0) {
69 152 syn->current_syntax->start_state = state;
70 }
71
72 931 return hashmap_insert(&syn->current_syntax->states, state->name, state);
73 }
74
75 2471 static State *reference_state (
76 const SyntaxLoader *syn,
77 ErrorBuffer *ebuf,
78 const char *name
79 ) {
80
2/2
✓ Branch 2 → 3 taken 666 times.
✓ Branch 2 → 4 taken 1805 times.
2471 if (streq(name, "this")) {
81 666 return syn->current_state;
82 }
83
84 1805 State *state = find_or_add_state(syn, name);
85
4/4
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 8 taken 1801 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 3 times.
1805 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 2 → 3 not taken.
✓ Branch 2 → 6 taken 272 times.
✗ Branch 4 → 5 not taken.
✗ Branch 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 3 → 4 not taken.
✓ Branch 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 6 → 7 not taken.
✓ Branch 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 3 → 4 taken 17 times.
✓ Branch 3 → 6 taken 158 times.
175 if (streq(ret, "END")) {
136
1/2
✓ Branch 5 → 9 taken 17 times.
✗ Branch 5 → 12 not taken.
17 if (!in_subsyntax(&e->syn, &e->err)) {
137 return false;
138 }
139
1/2
✓ Branch 6 → 7 taken 158 times.
✗ Branch 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 9 → 10 taken 175 times.
✗ Branch 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 2743 static bool destination_state(EditorState *e, const char *name, State **dest)
152 {
153 2743 const char *sep = strchr(name, ':');
154
2/2
✓ Branch 2 → 3 taken 175 times.
✓ Branch 2 → 6 taken 2568 times.
2743 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 6 → 7 taken 255 times.
✓ Branch 6 → 10 taken 2313 times.
2568 if (streq(name, "END")) {
163
1/2
✓ Branch 8 → 9 taken 255 times.
✗ Branch 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 2313 *dest = reference_state(&e->syn, &e->err, name);
171 2313 return true;
172 }
173
174 2171 static void lint_emit_name (
175 ErrorBuffer *ebuf,
176 SyntaxLoadFlags flags,
177 const char *ename,
178 const State *dest
179 ) {
180 2171 if (
181
2/2
✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 7 taken 2157 times.
2171 (flags & SYN_LINT)
182 14 && ename
183
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 7 taken 13 times.
14 && dest
184
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
1 && dest->emit_name
185
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 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 2171 }
194
195 1904 static Condition *add_condition (
196 EditorState *e,
197 ConditionType type,
198 const char *dest,
199 const char *emit
200 ) {
201 1904 BUG_ON(!dest && cond_type_has_destination(type));
202
1/2
✓ Branch 6 → 7 taken 1904 times.
✗ Branch 6 → 18 not taken.
1904 if (!in_state(&e->syn, &e->err)) {
203 return NULL;
204 }
205
206 1904 State *d = NULL;
207
3/4
✓ Branch 7 → 8 taken 1814 times.
✓ Branch 7 → 10 taken 90 times.
✓ Branch 9 → 10 taken 1814 times.
✗ Branch 9 → 18 not taken.
1904 if (dest && !destination_state(e, dest, &d)) {
208 return NULL;
209 }
210
211
2/2
✓ Branch 10 → 11 taken 551 times.
✓ Branch 10 → 12 taken 1353 times.
1904 emit = emit ? str_intern(emit) : NULL;
212
213 1904 if (
214 1904 type != COND_HEREDOCEND
215
2/2
✓ Branch 12 → 13 taken 1789 times.
✓ Branch 12 → 15 taken 115 times.
1904 && type != COND_INLIST
216
1/2
✓ Branch 13 → 14 taken 1789 times.
✗ Branch 13 → 15 not taken.
1789 && type != COND_INLIST_BUFFER
217 ) {
218 1789 lint_emit_name(&e->err, e->syn.flags, emit, d);
219 }
220
221 1904 Condition *c = xcalloc1(sizeof(*c));
222 1904 c->a.destination = d;
223 1904 c->a.emit_name = emit;
224 1904 c->type = type;
225 1904 ptr_array_append(&e->syn.current_state->conds, c);
226 1904 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 1561 static bool cmd_char(EditorState *e, const CommandArgs *a)
254 {
255 1561 const char *chars = a->args[0];
256
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1561 times.
1561 if (unlikely(chars[0] == '\0')) {
257 return error_msg(&e->err, "char argument can't be empty");
258 }
259
260 1561 bool add_to_buffer = cmdargs_has_flag(a, 'b');
261 1561 bool invert = cmdargs_has_flag(a, 'n');
262 1561 ConditionType type;
263
2/2
✓ Branch 6 → 7 taken 1326 times.
✓ Branch 6 → 10 taken 235 times.
1561 if (add_to_buffer) {
264 type = COND_CHAR_BUFFER;
265
4/4
✓ Branch 7 → 8 taken 1295 times.
✓ Branch 7 → 9 taken 31 times.
✓ Branch 8 → 9 taken 535 times.
✓ Branch 8 → 10 taken 760 times.
1326 } else if (!invert && chars[1] == '\0') {
266 type = COND_CHAR1;
267 } else {
268 566 type = COND_CHAR;
269 }
270
271 1561 Condition *c = add_condition(e, type, a->args[1], a->args[2]);
272
1/2
✓ Branch 11 → 12 taken 1561 times.
✗ Branch 11 → 17 not taken.
1561 if (!c) {
273 return false;
274 }
275
276
2/2
✓ Branch 12 → 13 taken 760 times.
✓ Branch 12 → 14 taken 801 times.
1561 if (type == COND_CHAR1) {
277 760 c->u.ch = (unsigned char)chars[0];
278 } else {
279 801 bitset_add_char_range(c->u.bitset, chars);
280
2/2
✓ Branch 15 → 16 taken 44 times.
✓ Branch 15 → 17 taken 757 times.
801 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 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 69 times.
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 14 → 9 taken 81 times.
✓ Branch 14 → 15 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 11 → 12 not taken.
✓ Branch 11 → 13 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 382 static bool cmd_eat(EditorState *e, const CommandArgs *a)
311 {
312 382 SyntaxLoader *syn = &e->syn;
313
1/2
✓ Branch 3 → 4 taken 382 times.
✗ Branch 3 → 10 not taken.
382 if (!in_state(syn, &e->err)) {
314 return false;
315 }
316
317 382 const char *dest = a->args[0];
318 382 State *curstate = syn->current_state;
319
1/2
✓ Branch 5 → 6 taken 382 times.
✗ Branch 5 → 10 not taken.
382 if (!destination_state(e, dest, &curstate->default_action.destination)) {
320 return false;
321 }
322
323
2/2
✓ Branch 6 → 7 taken 90 times.
✓ Branch 6 → 8 taken 292 times.
382 const char *emit = a->args[1] ? str_intern(a->args[1]) : NULL;
324 382 lint_emit_name(&e->err, syn->flags, emit, curstate->default_action.destination);
325 382 curstate->default_action.emit_name = emit;
326 382 curstate->type = STATE_EAT;
327 382 syn->current_state = NULL;
328 382 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 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 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 8 → 9 taken 8 times.
✓ Branch 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 12 → 13 not taken.
✓ Branch 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 18 → 16 taken 3640 times.
✓ Branch 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 3 → 4 taken 105 times.
✗ Branch 3 → 5 not taken.
105 ConditionType type = cmdargs_has_flag(a, 'b') ? COND_INLIST_BUFFER : COND_INLIST;
420
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);
421
422
1/2
✓ Branch 8 → 9 taken 105 times.
✗ Branch 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 10 → 11 taken 95 times.
✓ Branch 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 534 static bool cmd_noeat(EditorState *e, const CommandArgs *a)
439 {
440 534 SyntaxLoader *syn = &e->syn;
441 534 State *dest;
442
2/4
✓ Branch 3 → 4 taken 534 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 534 times.
534 if (unlikely(!in_state(syn, &e->err) || !destination_state(e, a->args[0], &dest))) {
443 return false;
444 }
445
446 534 State *curstate = syn->current_state;
447
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 533 times.
534 if (unlikely(dest == curstate)) {
448 1 return error_msg(&e->err, "using noeat to jump to same state causes infinite loop");
449 }
450
451 533 Action *defaction = &curstate->default_action;
452 533 bool bflag = cmdargs_has_flag(a, 'b');
453 533 defaction->destination = dest;
454 533 defaction->emit_name = NULL;
455
2/2
✓ Branch 10 → 11 taken 506 times.
✓ Branch 10 → 12 taken 27 times.
533 curstate->type = bflag ? STATE_NOEAT_BUFFER : STATE_NOEAT;
456 533 syn->current_state = NULL;
457 533 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 2 → 3 taken 58 times.
✓ Branch 2 → 8 taken 32 times.
90 if (len_str) {
468 58 type = COND_RECOLOR;
469
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 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 6 → 7 not taken.
✓ Branch 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 9 → 10 taken 90 times.
✗ Branch 9 → 12 not taken.
90 if (!c) {
479 return false;
480 }
481
482
2/2
✓ Branch 10 → 11 taken 58 times.
✓ Branch 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 2 → 3 not taken.
✓ Branch 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 6 → 7 taken 5 times.
✓ Branch 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 8 → 9 taken 5 times.
✗ Branch 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 930 static bool cmd_state(EditorState *e, const CommandArgs *a)
526 {
527 930 SyntaxLoader *syn = &e->syn;
528 930 ErrorBuffer *ebuf = &e->err;
529
2/4
✓ Branch 3 → 4 taken 930 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 930 times.
930 if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) {
530 return false;
531 }
532
533 930 const char *name = a->args[0];
534
2/4
✓ Branch 7 → 8 taken 930 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 930 times.
930 if (unlikely(streq(name, "END") || streq(name, "this"))) {
535 return error_msg(ebuf, "'%s' is reserved state name", name);
536 }
537
538 930 State *state = find_or_add_state(syn, name);
539
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 930 times.
930 if (unlikely(state->defined)) {
540 return error_msg(ebuf, "State '%s' already exists", name);
541 }
542
543 930 const char *emit_name = a->args[1];
544
3/6
✓ Branch 13 → 14 taken 16 times.
✓ Branch 13 → 20 taken 914 times.
✓ Branch 14 → 15 taken 16 times.
✗ Branch 14 → 16 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 19 not taken.
930 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 930 state->defined = true;
550
2/2
✓ Branch 20 → 21 taken 287 times.
✓ Branch 20 → 22 taken 627 times.
1217 state->emit_name = str_intern(emit_name ? emit_name : name);
551 930 syn->current_state = state;
552 930 return true;
553 }
554
555 120 static bool cmd_str(EditorState *e, const CommandArgs *a)
556 {
557 120 const char *str = a->args[0];
558 120 size_t len = strlen(str);
559
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 120 times.
120 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 120 Condition *c;
567 120 size_t maxlen = ARRAYLEN(c->u.str.buf);
568
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 120 times.
120 if (unlikely(len > maxlen)) {
569 return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen);
570 }
571
572 120 ConditionType type;
573
2/2
✓ Branch 7 → 8 taken 113 times.
✓ Branch 7 → 10 taken 7 times.
120 if (cmdargs_has_flag(a, 'i')) {
574 type = COND_STR_ICASE;
575 } else {
576
2/2
✓ Branch 8 → 9 taken 36 times.
✓ Branch 8 → 10 taken 77 times.
113 type = (len == 2) ? COND_STR2 : COND_STR;
577 }
578
579 120 c = add_condition(e, type, a->args[1], a->args[2]);
580
1/2
✓ Branch 11 → 12 taken 120 times.
✗ Branch 11 → 13 not taken.
120 if (!c) {
581 return false;
582 }
583
584 120 memcpy(c->u.str.buf, str, len);
585 120 c->u.str.len = len;
586 120 return true;
587 }
588
589 152 static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes)
590 {
591 152 Syntax *syntax = syn->current_syntax;
592 152 BUG_ON(!syntax);
593
4/4
✓ Branch 5 → 6 taken 151 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 9 taken 146 times.
152 bool r = close_state(syn, ebuf) && finalize_syntax(syntaxes, syntax, ebuf);
594 6 if (!r) {
595 6 free_syntax(syntax);
596 }
597 152 syn->current_syntax = NULL;
598 152 return r;
599 }
600
601 153 static bool cmd_syntax(EditorState *e, const CommandArgs *a)
602 {
603 153 SyntaxLoader *syn = &e->syn;
604
3/4
✓ Branch 2 → 3 taken 83 times.
✓ Branch 2 → 5 taken 70 times.
✓ Branch 4 → 5 taken 83 times.
✗ Branch 4 → 11 not taken.
153 if (syn->current_syntax && !finish_syntax(syn, &e->err, &e->syntaxes)) {
605 return false;
606 }
607
608 153 Syntax *syntax = xcalloc1(sizeof(*syntax));
609 153 syntax->name = xstrdup(a->args[0]);
610
4/4
✓ Branch 7 → 8 taken 82 times.
✓ Branch 7 → 10 taken 71 times.
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 10 taken 75 times.
153 if (is_subsyntax(syntax) && !(syn->flags & SYN_WARN_ON_UNUSED_SUBSYN)) {
611 7 syntax->warned_unused_subsyntax = true;
612 }
613
614 153 syn->current_syntax = syntax;
615 153 syn->current_state = NULL;
616 153 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 4104 static const Command *find_syntax_command(const char *name)
645 {
646 4104 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 76 static CommandRunner cmdrunner_for_syntaxes(EditorState *e)
662 {
663 76 CommandRunner runner = cmdrunner(e, &syntax_commands);
664 76 runner.expand_variable = expand_syntax_var;
665 76 runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR;
666 76 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 71 Syntax *load_syntax (
687 EditorState *e,
688 StringView config_text,
689 const char *config_filename,
690 SyntaxLoadFlags flags
691 ) {
692 71 SyntaxLoader *syn = &e->syn;
693 71 *syn = (SyntaxLoader) {
694 .current_syntax = NULL,
695 .current_state = NULL,
696 71 .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN,
697 };
698
699 71 ErrorBuffer *ebuf = &e->err;
700 71 const char *saved_file = ebuf->config_filename;
701 71 const unsigned int saved_line = ebuf->config_line;
702 71 CommandRunner runner = cmdrunner_for_syntaxes(e);
703
704 71 ebuf->config_filename = config_filename;
705 71 ebuf->config_line = 1;
706 71 bool r = exec_config(&runner, config_text);
707
708
2/2
✓ Branch 3 → 4 taken 70 times.
✓ Branch 3 → 10 taken 1 time.
71 if (syn->current_syntax) {
709
2/2
✓ Branch 4 → 5 taken 69 times.
✓ Branch 4 → 8 taken 1 time.
70 if (r) {
710 69 r = finish_syntax(syn, ebuf, &e->syntaxes);
711
2/2
✓ Branch 6 → 7 taken 63 times.
✓ Branch 6 → 10 taken 6 times.
69 if (r) {
712 63 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 71 ebuf->config_filename = saved_file;
721 71 ebuf->config_line = saved_line;
722
723
2/2
✓ Branch 10 → 11 taken 64 times.
✓ Branch 10 → 17 taken 7 times.
71 if (!r) {
724 return NULL;
725 }
726
727 64 const char *base = path_basename(config_filename);
728 64 Syntax *syntax = find_syntax(&e->syntaxes, base);
729
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 15 taken 62 times.
64 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 15 → 16 not taken.
✓ Branch 15 → 17 taken 62 times.
62 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 2 → 3 taken 55 times.
✗ Branch 2 → 6 not taken.
55 if (!cfg) {
750
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 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 3 → 4 taken 55 times.
✓ Branch 3 → 10 taken 2 times.
57 if (size < 0) {
763 55 int err = errno;
764
2/4
✓ Branch 4 → 5 taken 55 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 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 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 1 time.
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 2 → 3 taken 55 times.
✗ Branch 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 5 → 6 taken 55 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 55 times.
✗ Branch 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