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/path.h" | ||
18 | #include "util/readfile.h" | ||
19 | #include "util/strtonum.h" | ||
20 | #include "util/xmalloc.h" | ||
21 | #include "util/xsnprintf.h" | ||
22 | #include "util/xstring.h" | ||
23 | |||
24 | 3901 | static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf) | |
25 | { | ||
26 |
1/4✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 3901 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
|
3901 | return likely(syn->current_syntax) || error_msg(ebuf, "No syntax started"); |
27 | } | ||
28 | |||
29 | 2810 | static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf) | |
30 | { | ||
31 |
1/2✓ Branch 0 (3→4) taken 2810 times.
✗ Branch 1 (3→9) not taken.
|
2810 | if (unlikely(!in_syntax(syn, ebuf))) { |
32 | return false; | ||
33 | } | ||
34 |
1/4✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→8) taken 2810 times.
✗ Branch 2 (6→7) not taken.
✗ Branch 3 (6→8) not taken.
|
2810 | return likely(syn->current_state) || error_msg(ebuf, "No state started"); |
35 | } | ||
36 | |||
37 | 1242 | static bool close_state(SyntaxLoader *syn, ErrorBuffer *ebuf) | |
38 | { | ||
39 | 1242 | const State *state = syn->current_state; | |
40 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→5) taken 1241 times.
|
1242 | if (!state) { |
41 | return true; | ||
42 | } | ||
43 | |||
44 | 1 | syn->current_state = NULL; | |
45 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (likely(state->type != STATE_INVALID)) { |
46 | return true; | ||
47 | } | ||
48 | |||
49 | // This error applies to the state itself rather than the last command, so | ||
50 | // it doesn't make sense to include the command name in the error message | ||
51 | 1 | const char *name = state->name; | |
52 | 1 | return error_msg_for_cmd(ebuf, NULL, "No default action in state '%s'", name); | |
53 | } | ||
54 | |||
55 | 2714 | static State *find_or_add_state(const SyntaxLoader *syn, const char *name) | |
56 | { | ||
57 | 2714 | State *state = find_state(syn->current_syntax, name); | |
58 |
2/2✓ Branch 0 (3→4) taken 924 times.
✓ Branch 1 (3→9) taken 1790 times.
|
2714 | if (state) { |
59 | return state; | ||
60 | } | ||
61 | |||
62 | 924 | state = xcalloc1(sizeof(*state)); | |
63 | 924 | state->name = xstrdup(name); | |
64 | 924 | state->defined = false; | |
65 | 924 | state->type = STATE_INVALID; | |
66 | |||
67 |
2/2✓ Branch 0 (6→7) taken 151 times.
✓ Branch 1 (6→8) taken 773 times.
|
924 | if (syn->current_syntax->states.count == 0) { |
68 | 151 | syn->current_syntax->start_state = state; | |
69 | } | ||
70 | |||
71 | 924 | return hashmap_insert(&syn->current_syntax->states, state->name, state); | |
72 | } | ||
73 | |||
74 | 2448 | static State *reference_state ( | |
75 | const SyntaxLoader *syn, | ||
76 | ErrorBuffer *ebuf, | ||
77 | const char *name | ||
78 | ) { | ||
79 |
2/2✓ Branch 0 (2→3) taken 657 times.
✓ Branch 1 (2→4) taken 1791 times.
|
2448 | if (streq(name, "this")) { |
80 | 657 | return syn->current_state; | |
81 | } | ||
82 | |||
83 | 1791 | State *state = find_or_add_state(syn, name); | |
84 |
4/4✓ Branch 0 (5→6) taken 4 times.
✓ Branch 1 (5→8) taken 1787 times.
✓ Branch 2 (6→7) taken 1 times.
✓ Branch 3 (6→8) taken 3 times.
|
1791 | if (unlikely((syn->flags & SYN_LINT) && state == syn->current_state)) { |
85 | 1 | error_msg ( // Soft error | |
86 | ebuf, | ||
87 | "destination '%s' can be optimized to 'this' in '%s' syntax", | ||
88 | name, | ||
89 | 1 | syn->current_syntax->name | |
90 | ); | ||
91 | } | ||
92 | |||
93 | return state; | ||
94 | } | ||
95 | |||
96 | 272 | static bool in_subsyntax(const SyntaxLoader *syn, ErrorBuffer *ebuf) | |
97 | { | ||
98 | 272 | bool ss = likely(is_subsyntax(syn->current_syntax)); | |
99 |
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"); |
100 | } | ||
101 | |||
102 | 188 | static Syntax *must_find_subsyntax ( | |
103 | const HashMap *syntaxes, | ||
104 | ErrorBuffer *ebuf, | ||
105 | const char *name | ||
106 | ) { | ||
107 | 188 | Syntax *syntax = find_any_syntax(syntaxes, name); | |
108 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 188 times.
|
188 | if (unlikely(!syntax)) { |
109 | ✗ | error_msg(ebuf, "No such syntax '%s'", name); | |
110 | ✗ | return NULL; | |
111 | } | ||
112 |
1/2✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 188 times.
|
188 | if (unlikely(!is_subsyntax(syntax))) { |
113 | ✗ | error_msg(ebuf, "Syntax '%s' is not a subsyntax", name); | |
114 | ✗ | return NULL; | |
115 | } | ||
116 | return syntax; | ||
117 | } | ||
118 | |||
119 | 175 | static bool subsyntax_call ( | |
120 | EditorState *e, | ||
121 | const char *name, | ||
122 | const char *ret, | ||
123 | State **dest | ||
124 | ) { | ||
125 | 175 | Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, name); | |
126 | |||
127 | 175 | SyntaxMerge m = { | |
128 | .subsyn = subsyn, | ||
129 | .return_state = NULL, | ||
130 | .delim = NULL, | ||
131 | .delim_len = 0, | ||
132 | }; | ||
133 | |||
134 |
2/2✓ Branch 0 (3→4) taken 17 times.
✓ Branch 1 (3→6) taken 158 times.
|
175 | if (streq(ret, "END")) { |
135 |
1/2✓ Branch 0 (5→9) taken 17 times.
✗ Branch 1 (5→12) not taken.
|
17 | if (!in_subsyntax(&e->syn, &e->err)) { |
136 | return false; | ||
137 | } | ||
138 |
1/2✓ Branch 0 (6→7) taken 158 times.
✗ Branch 1 (6→12) not taken.
|
158 | } else if (subsyn) { |
139 | 158 | m.return_state = reference_state(&e->syn, &e->err, ret); | |
140 | } | ||
141 | |||
142 |
1/2✓ Branch 0 (9→10) taken 175 times.
✗ Branch 1 (9→12) not taken.
|
175 | if (subsyn) { |
143 | 175 | *dest = merge_syntax(e->syn.current_syntax, &m, &e->styles); | |
144 | 175 | return true; | |
145 | } | ||
146 | |||
147 | return false; | ||
148 | } | ||
149 | |||
150 | 2720 | static bool destination_state(EditorState *e, const char *name, State **dest) | |
151 | { | ||
152 | 2720 | const char *sep = strchr(name, ':'); | |
153 |
2/2✓ Branch 0 (2→3) taken 175 times.
✓ Branch 1 (2→6) taken 2545 times.
|
2720 | if (sep) { |
154 | // subsyntax:returnstate | ||
155 | 175 | char *sub = xstrcut(name, sep - name); | |
156 | 175 | bool success = subsyntax_call(e, sub, sep + 1, dest); | |
157 | 175 | free(sub); | |
158 | 175 | return success; | |
159 | } | ||
160 | |||
161 |
2/2✓ Branch 0 (6→7) taken 255 times.
✓ Branch 1 (6→10) taken 2290 times.
|
2545 | if (streq(name, "END")) { |
162 |
1/2✓ Branch 0 (8→9) taken 255 times.
✗ Branch 1 (8→12) not taken.
|
255 | if (!in_subsyntax(&e->syn, &e->err)) { |
163 | return false; | ||
164 | } | ||
165 | 255 | *dest = NULL; | |
166 | 255 | return true; | |
167 | } | ||
168 | |||
169 | 2290 | *dest = reference_state(&e->syn, &e->err, name); | |
170 | 2290 | return true; | |
171 | } | ||
172 | |||
173 | 2150 | static void lint_emit_name ( | |
174 | ErrorBuffer *ebuf, | ||
175 | SyntaxLoadFlags flags, | ||
176 | const char *ename, | ||
177 | const State *dest | ||
178 | ) { | ||
179 | 2150 | if ( | |
180 |
2/2✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→7) taken 2136 times.
|
2150 | (flags & SYN_LINT) |
181 | 14 | && ename | |
182 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→7) taken 13 times.
|
14 | && dest |
183 |
1/2✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→7) not taken.
|
1 | && dest->emit_name |
184 |
1/2✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→7) not taken.
|
1 | && interned_strings_equal(ename, dest->emit_name) |
185 | ) { | ||
186 | 1 | error_msg ( // Soft error | |
187 | ebuf, | ||
188 | "emit-name '%s' not needed (destination state uses same emit-name)", | ||
189 | ename | ||
190 | ); | ||
191 | } | ||
192 | 2150 | } | |
193 | |||
194 | 1888 | static Condition *add_condition ( | |
195 | EditorState *e, | ||
196 | ConditionType type, | ||
197 | const char *dest, | ||
198 | const char *emit | ||
199 | ) { | ||
200 | 1888 | BUG_ON(!dest && cond_type_has_destination(type)); | |
201 |
1/2✓ Branch 0 (6→7) taken 1888 times.
✗ Branch 1 (6→18) not taken.
|
1888 | if (!in_state(&e->syn, &e->err)) { |
202 | return NULL; | ||
203 | } | ||
204 | |||
205 | 1888 | State *d = NULL; | |
206 |
3/4✓ Branch 0 (7→8) taken 1798 times.
✓ Branch 1 (7→10) taken 90 times.
✓ Branch 2 (9→10) taken 1798 times.
✗ Branch 3 (9→18) not taken.
|
1888 | if (dest && !destination_state(e, dest, &d)) { |
207 | return NULL; | ||
208 | } | ||
209 | |||
210 |
2/2✓ Branch 0 (10→11) taken 548 times.
✓ Branch 1 (10→12) taken 1340 times.
|
1888 | emit = emit ? str_intern(emit) : NULL; |
211 | |||
212 | 1888 | if ( | |
213 | 1888 | type != COND_HEREDOCEND | |
214 |
2/2✓ Branch 0 (12→13) taken 1773 times.
✓ Branch 1 (12→15) taken 115 times.
|
1888 | && type != COND_INLIST |
215 |
1/2✓ Branch 0 (13→14) taken 1773 times.
✗ Branch 1 (13→15) not taken.
|
1773 | && type != COND_INLIST_BUFFER |
216 | ) { | ||
217 | 1773 | lint_emit_name(&e->err, e->syn.flags, emit, d); | |
218 | } | ||
219 | |||
220 | 1888 | Condition *c = xcalloc1(sizeof(*c)); | |
221 | 1888 | c->a.destination = d; | |
222 | 1888 | c->a.emit_name = emit; | |
223 | 1888 | c->type = type; | |
224 | 1888 | ptr_array_append(&e->syn.current_state->conds, c); | |
225 | 1888 | return c; | |
226 | } | ||
227 | |||
228 | 18 | static bool cmd_bufis(EditorState *e, const CommandArgs *a) | |
229 | { | ||
230 | 18 | const char *str = a->args[0]; | |
231 | 18 | const size_t len = strlen(str); | |
232 | 18 | Condition *c; | |
233 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 18 times.
|
18 | if (unlikely(len > ARRAYLEN(c->u.str.buf))) { |
234 | ✗ | return error_msg ( | |
235 | &e->err, | ||
236 | "Maximum length of string is %zu bytes", | ||
237 | ARRAYLEN(c->u.str.buf) | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | 18 | ConditionType type = a->flags[0] == 'i' ? COND_BUFIS_ICASE : COND_BUFIS; | |
242 | 18 | c = add_condition(e, type, a->args[1], a->args[2]); | |
243 |
1/2✓ Branch 0 (5→6) taken 18 times.
✗ Branch 1 (5→7) not taken.
|
18 | if (!c) { |
244 | return false; | ||
245 | } | ||
246 | |||
247 | 18 | memcpy(c->u.str.buf, str, len); | |
248 | 18 | c->u.str.len = len; | |
249 | 18 | return true; | |
250 | } | ||
251 | |||
252 | 1547 | static bool cmd_char(EditorState *e, const CommandArgs *a) | |
253 | { | ||
254 | 1547 | const char *chars = a->args[0]; | |
255 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1547 times.
|
1547 | if (unlikely(chars[0] == '\0')) { |
256 | ✗ | return error_msg(&e->err, "char argument can't be empty"); | |
257 | } | ||
258 | |||
259 | 1547 | bool add_to_buffer = cmdargs_has_flag(a, 'b'); | |
260 | 1547 | bool invert = cmdargs_has_flag(a, 'n'); | |
261 | 1547 | ConditionType type; | |
262 |
2/2✓ Branch 0 (6→7) taken 1312 times.
✓ Branch 1 (6→10) taken 235 times.
|
1547 | if (add_to_buffer) { |
263 | type = COND_CHAR_BUFFER; | ||
264 |
4/4✓ Branch 0 (7→8) taken 1281 times.
✓ Branch 1 (7→9) taken 31 times.
✓ Branch 2 (8→9) taken 534 times.
✓ Branch 3 (8→10) taken 747 times.
|
1312 | } else if (!invert && chars[1] == '\0') { |
265 | type = COND_CHAR1; | ||
266 | } else { | ||
267 | 565 | type = COND_CHAR; | |
268 | } | ||
269 | |||
270 | 1547 | Condition *c = add_condition(e, type, a->args[1], a->args[2]); | |
271 |
1/2✓ Branch 0 (11→12) taken 1547 times.
✗ Branch 1 (11→17) not taken.
|
1547 | if (!c) { |
272 | return false; | ||
273 | } | ||
274 | |||
275 |
2/2✓ Branch 0 (12→13) taken 747 times.
✓ Branch 1 (12→14) taken 800 times.
|
1547 | if (type == COND_CHAR1) { |
276 | 747 | c->u.ch = (unsigned char)chars[0]; | |
277 | } else { | ||
278 | 800 | bitset_add_char_range(c->u.bitset, chars); | |
279 |
2/2✓ Branch 0 (15→16) taken 44 times.
✓ Branch 1 (15→17) taken 756 times.
|
800 | if (invert) { |
280 | 44 | BITSET_INVERT(c->u.bitset); | |
281 | } | ||
282 | } | ||
283 | |||
284 | return true; | ||
285 | } | ||
286 | |||
287 | 66 | static bool cmd_default(EditorState *e, const CommandArgs *a) | |
288 | { | ||
289 | 66 | SyntaxLoader *syn = &e->syn; | |
290 | 66 | ErrorBuffer *ebuf = &e->err; | |
291 |
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)) { |
292 | ✗ | return false; | |
293 | } | ||
294 | |||
295 | 66 | const char *value = str_intern(a->args[0]); | |
296 | 66 | HashMap *map = &syn->current_syntax->default_styles; | |
297 |
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++) { |
298 | 78 | const char *name = a->args[i]; | |
299 | 78 | const void *oldval = hashmap_insert_or_replace(map, xstrdup(name), (char*)value); | |
300 |
1/2✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 78 times.
|
78 | if (unlikely(oldval)) { |
301 | // Soft error | ||
302 | ✗ | error_msg(ebuf, "'%s' argument specified multiple times", name); | |
303 | } | ||
304 | } | ||
305 | |||
306 | return true; | ||
307 | } | ||
308 | |||
309 | 377 | static bool cmd_eat(EditorState *e, const CommandArgs *a) | |
310 | { | ||
311 | 377 | SyntaxLoader *syn = &e->syn; | |
312 |
1/2✓ Branch 0 (3→4) taken 377 times.
✗ Branch 1 (3→10) not taken.
|
377 | if (!in_state(syn, &e->err)) { |
313 | return false; | ||
314 | } | ||
315 | |||
316 | 377 | const char *dest = a->args[0]; | |
317 | 377 | State *curstate = syn->current_state; | |
318 |
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)) { |
319 | return false; | ||
320 | } | ||
321 | |||
322 |
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; |
323 | 377 | lint_emit_name(&e->err, syn->flags, emit, curstate->default_action.destination); | |
324 | 377 | curstate->default_action.emit_name = emit; | |
325 | 377 | curstate->type = STATE_EAT; | |
326 | 377 | syn->current_state = NULL; | |
327 | 377 | return true; | |
328 | } | ||
329 | |||
330 | 13 | static bool cmd_heredocbegin(EditorState *e, const CommandArgs *a) | |
331 | { | ||
332 | 13 | SyntaxLoader *syn = &e->syn; | |
333 |
1/2✓ Branch 0 (3→4) taken 13 times.
✗ Branch 1 (3→9) not taken.
|
13 | if (!in_state(syn, &e->err)) { |
334 | return false; | ||
335 | } | ||
336 | |||
337 | 13 | Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, a->args[0]); | |
338 |
1/2✓ Branch 0 (5→6) taken 13 times.
✗ Branch 1 (5→9) not taken.
|
13 | if (!subsyn) { |
339 | return false; | ||
340 | } | ||
341 | |||
342 | // default_action.destination is used as the return state | ||
343 | 13 | const char *ret = a->args[1]; | |
344 |
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)) { |
345 | return false; | ||
346 | } | ||
347 | |||
348 | 13 | syn->current_state->default_action.emit_name = NULL; | |
349 | 13 | syn->current_state->type = STATE_HEREDOCBEGIN; | |
350 | 13 | syn->current_state->heredoc.subsyntax = subsyn; | |
351 | 13 | syn->current_state = NULL; | |
352 | |||
353 | // Normally merge() marks subsyntax used but in case of heredocs merge() | ||
354 | // is not called when syntax file is loaded | ||
355 | 13 | subsyn->used = true; | |
356 | 13 | return true; | |
357 | } | ||
358 | |||
359 | 10 | static bool cmd_heredocend(EditorState *e, const CommandArgs *a) | |
360 | { | ||
361 | 10 | Condition *c = add_condition(e, COND_HEREDOCEND, a->args[0], a->args[1]); | |
362 |
1/2✓ Branch 0 (3→4) taken 10 times.
✗ Branch 1 (3→7) not taken.
|
10 | if (unlikely(!c)) { |
363 | return false; | ||
364 | } | ||
365 | |||
366 | 10 | Syntax *current_syntax = e->syn.current_syntax; | |
367 | 10 | BUG_ON(!current_syntax); | |
368 | 10 | current_syntax->heredoc = true; | |
369 | 10 | return true; | |
370 | } | ||
371 | |||
372 | // Forward declaration, used in cmd_include() and cmd_require() | ||
373 | static int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags); | ||
374 | |||
375 | ✗ | static bool cmd_include(EditorState *e, const CommandArgs *a) | |
376 | { | ||
377 | ✗ | SyntaxLoadFlags flags = SYN_MUST_EXIST; | |
378 | ✗ | if (a->flags[0] == 'b') { | |
379 | ✗ | flags |= SYN_BUILTIN; | |
380 | } | ||
381 | ✗ | int r = read_syntax(e, a->args[0], flags); | |
382 | ✗ | return r == 0; | |
383 | } | ||
384 | |||
385 | 102 | static bool cmd_list(EditorState *e, const CommandArgs *a) | |
386 | { | ||
387 | 102 | SyntaxLoader *syn = &e->syn; | |
388 | 102 | ErrorBuffer *ebuf = &e->err; | |
389 |
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)) { |
390 | ✗ | return false; | |
391 | } | ||
392 | |||
393 | 102 | char **args = a->args; | |
394 | 102 | const char *name = args[0]; | |
395 | 102 | StringList *list = find_string_list(syn->current_syntax, name); | |
396 |
2/2✓ Branch 0 (8→9) taken 8 times.
✓ Branch 1 (8→12) taken 94 times.
|
102 | if (!list) { |
397 | 8 | list = xcalloc1(sizeof(*list)); | |
398 | 8 | hashmap_insert(&syn->current_syntax->string_lists, xstrdup(name), list); | |
399 |
1/2✗ Branch 0 (12→13) not taken.
✓ Branch 1 (12→14) taken 94 times.
|
94 | } else if (unlikely(list->defined)) { |
400 | ✗ | return error_msg(ebuf, "List '%s' already exists", name); | |
401 | } | ||
402 | 102 | list->defined = true; | |
403 | |||
404 | 102 | bool icase = a->flags[0] == 'i'; | |
405 | 102 | HashSet *set = &list->strings; | |
406 | 102 | hashset_init(set, a->nr_args - 1, icase); | |
407 |
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++) { |
408 | 3640 | const char *str = args[i]; | |
409 | 3640 | hashset_insert(set, 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 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; |
419 |
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); |
420 | |||
421 |
1/2✓ Branch 0 (8→9) taken 105 times.
✗ Branch 1 (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 0 (10→11) taken 95 times.
✓ Branch 1 (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 | 532 | static bool cmd_noeat(EditorState *e, const CommandArgs *a) | |
438 | { | ||
439 | 532 | SyntaxLoader *syn = &e->syn; | |
440 | 532 | State *dest; | |
441 |
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))) { |
442 | ✗ | return false; | |
443 | } | ||
444 | |||
445 | 532 | State *curstate = syn->current_state; | |
446 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 531 times.
|
532 | if (unlikely(dest == curstate)) { |
447 | 1 | return error_msg(&e->err, "using noeat to jump to same state causes infinite loop"); | |
448 | } | ||
449 | |||
450 | 531 | Action *defaction = &curstate->default_action; | |
451 | 531 | bool bflag = cmdargs_has_flag(a, 'b'); | |
452 | 531 | defaction->destination = dest; | |
453 | 531 | defaction->emit_name = NULL; | |
454 |
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; |
455 | 531 | syn->current_state = NULL; | |
456 | 531 | return true; | |
457 | } | ||
458 | |||
459 | 90 | static bool cmd_recolor(EditorState *e, const CommandArgs *a) | |
460 | { | ||
461 | // If length is not specified then buffered bytes will be recolored | ||
462 | 90 | ConditionType type = COND_RECOLOR_BUFFER; | |
463 | 90 | size_t len = 0; | |
464 | |||
465 | 90 | const char *len_str = a->args[1]; | |
466 |
2/2✓ Branch 0 (2→3) taken 58 times.
✓ Branch 1 (2→8) taken 32 times.
|
90 | if (len_str) { |
467 | 58 | type = COND_RECOLOR; | |
468 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (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 0 (6→7) not taken.
✓ Branch 1 (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 | 90 | Condition *c = add_condition(e, type, NULL, a->args[0]); | |
477 |
1/2✓ Branch 0 (9→10) taken 90 times.
✗ Branch 1 (9→12) not taken.
|
90 | if (!c) { |
478 | return false; | ||
479 | } | ||
480 | |||
481 |
2/2✓ Branch 0 (10→11) taken 58 times.
✓ Branch 1 (10→12) taken 32 times.
|
90 | if (type == COND_RECOLOR) { |
482 | 58 | c->u.recolor_len = len; | |
483 | } | ||
484 | |||
485 | return true; | ||
486 | } | ||
487 | |||
488 | 17 | static bool cmd_require(EditorState *e, const CommandArgs *a) | |
489 | { | ||
490 | 17 | char buf[8192]; | |
491 | 17 | char *path; | |
492 | 17 | size_t path_len; | |
493 | 17 | HashSet *set; | |
494 | 17 | SyntaxLoadFlags flags = SYN_MUST_EXIST; | |
495 | |||
496 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 17 times.
|
17 | if (a->flags[0] == 'f') { |
497 | ✗ | set = &e->required_syntax_files; | |
498 | ✗ | path = a->args[0]; | |
499 | ✗ | path_len = strlen(path); | |
500 | } else { | ||
501 | 17 | set = &e->required_syntax_builtins; | |
502 | 17 | path_len = xsnprintf(buf, sizeof(buf), "syntax/inc/%s", a->args[0]); | |
503 | 17 | path = buf; | |
504 | 17 | flags |= SYN_BUILTIN; | |
505 | } | ||
506 | |||
507 |
2/2✓ Branch 0 (6→7) taken 5 times.
✓ Branch 1 (6→11) taken 12 times.
|
17 | 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 | int r = read_syntax(e, path, flags); | |
515 | 5 | syn->flags = save; | |
516 |
1/2✓ Branch 0 (8→9) taken 5 times.
✗ Branch 1 (8→11) not taken.
|
5 | if (r != 0) { |
517 | return false; | ||
518 | } | ||
519 | |||
520 | 5 | hashset_insert(set, path, path_len); | |
521 | 5 | return true; | |
522 | } | ||
523 | |||
524 | 923 | static bool cmd_state(EditorState *e, const CommandArgs *a) | |
525 | { | ||
526 | 923 | SyntaxLoader *syn = &e->syn; | |
527 | 923 | ErrorBuffer *ebuf = &e->err; | |
528 |
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)) { |
529 | ✗ | return false; | |
530 | } | ||
531 | |||
532 | 923 | const char *name = a->args[0]; | |
533 |
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"))) { |
534 | ✗ | return error_msg(ebuf, "'%s' is reserved state name", name); | |
535 | } | ||
536 | |||
537 | 923 | State *state = find_or_add_state(syn, name); | |
538 |
1/2✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 923 times.
|
923 | if (unlikely(state->defined)) { |
539 | ✗ | return error_msg(ebuf, "State '%s' already exists", name); | |
540 | } | ||
541 | |||
542 | 923 | const char *emit_name = a->args[1]; | |
543 |
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)) { |
544 | // Soft error | ||
545 | ✗ | error_msg(ebuf, "Redundant emit-name '%s'", emit_name); | |
546 | } | ||
547 | |||
548 | 923 | state->defined = true; | |
549 |
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); |
550 | 923 | syn->current_state = state; | |
551 | 923 | return true; | |
552 | } | ||
553 | |||
554 | 118 | static bool cmd_str(EditorState *e, const CommandArgs *a) | |
555 | { | ||
556 | 118 | const char *str = a->args[0]; | |
557 | 118 | size_t len = strlen(str); | |
558 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 118 times.
|
118 | 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 | 118 | Condition *c; | |
566 | 118 | size_t maxlen = ARRAYLEN(c->u.str.buf); | |
567 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 118 times.
|
118 | if (unlikely(len > maxlen)) { |
568 | ✗ | return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen); | |
569 | } | ||
570 | |||
571 | 118 | ConditionType type; | |
572 |
2/2✓ Branch 0 (7→8) taken 111 times.
✓ Branch 1 (7→10) taken 7 times.
|
118 | if (cmdargs_has_flag(a, 'i')) { |
573 | type = COND_STR_ICASE; | ||
574 | } else { | ||
575 |
2/2✓ Branch 0 (8→9) taken 36 times.
✓ Branch 1 (8→10) taken 75 times.
|
111 | type = (len == 2) ? COND_STR2 : COND_STR; |
576 | } | ||
577 | |||
578 | 118 | c = add_condition(e, type, a->args[1], a->args[2]); | |
579 |
1/2✓ Branch 0 (11→12) taken 118 times.
✗ Branch 1 (11→13) not taken.
|
118 | if (!c) { |
580 | return false; | ||
581 | } | ||
582 | |||
583 | 118 | memcpy(c->u.str.buf, str, len); | |
584 | 118 | c->u.str.len = len; | |
585 | 118 | return true; | |
586 | } | ||
587 | |||
588 | 151 | static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes) | |
589 | { | ||
590 | 151 | Syntax *syntax = syn->current_syntax; | |
591 | 151 | BUG_ON(!syntax); | |
592 |
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); |
593 | 6 | if (!r) { | |
594 | 6 | free_syntax(syntax); | |
595 | } | ||
596 | 151 | syn->current_syntax = NULL; | |
597 | 151 | return r; | |
598 | } | ||
599 | |||
600 | 152 | static bool cmd_syntax(EditorState *e, const CommandArgs *a) | |
601 | { | ||
602 | 152 | SyntaxLoader *syn = &e->syn; | |
603 |
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)) { |
604 | return false; | ||
605 | } | ||
606 | |||
607 | 152 | Syntax *syntax = xcalloc1(sizeof(*syntax)); | |
608 | 152 | syntax->name = xstrdup(a->args[0]); | |
609 |
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)) { |
610 | 7 | syntax->warned_unused_subsyntax = true; | |
611 | } | ||
612 | |||
613 | 152 | syn->current_syntax = syntax; | |
614 | 152 | syn->current_state = NULL; | |
615 | 152 | 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_BSEARCH_ARRAY(cmds, name); | |
641 | 24 | } | |
642 | |||
643 | 4070 | static const Command *find_syntax_command(const char *name) | |
644 | { | ||
645 | 4070 | 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 | 75 | static CommandRunner cmdrunner_for_syntaxes(EditorState *e) | |
661 | { | ||
662 | 75 | CommandRunner runner = cmdrunner(e, &syntax_commands); | |
663 | 75 | runner.expand_variable = expand_syntax_var; | |
664 | 75 | runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR; | |
665 | 75 | 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 int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags) | |
677 | { | ||
678 | 5 | CommandRunner runner = cmdrunner_for_syntaxes(e); | |
679 | 5 | return read_config(&runner, filename, syn_flags_to_cfg_flags(flags)); | |
680 | } | ||
681 | |||
682 | 70 | Syntax *load_syntax ( | |
683 | EditorState *e, | ||
684 | StringView config_text, | ||
685 | const char *config_filename, | ||
686 | SyntaxLoadFlags flags | ||
687 | ) { | ||
688 | 70 | SyntaxLoader *syn = &e->syn; | |
689 | 70 | *syn = (SyntaxLoader) { | |
690 | .current_syntax = NULL, | ||
691 | .current_state = NULL, | ||
692 | 70 | .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN, | |
693 | }; | ||
694 | |||
695 | 70 | ErrorBuffer *ebuf = &e->err; | |
696 | 70 | const char *saved_file = ebuf->config_filename; | |
697 | 70 | const unsigned int saved_line = ebuf->config_line; | |
698 | 70 | CommandRunner runner = cmdrunner_for_syntaxes(e); | |
699 | |||
700 | 70 | ebuf->config_filename = config_filename; | |
701 | 70 | ebuf->config_line = 1; | |
702 | 70 | bool r = exec_config(&runner, config_text); | |
703 | |||
704 |
2/2✓ Branch 0 (3→4) taken 69 times.
✓ Branch 1 (3→10) taken 1 times.
|
70 | if (syn->current_syntax) { |
705 |
2/2✓ Branch 0 (4→5) taken 68 times.
✓ Branch 1 (4→8) taken 1 times.
|
69 | if (r) { |
706 | 68 | r = finish_syntax(syn, ebuf, &e->syntaxes); | |
707 |
2/2✓ Branch 0 (6→7) taken 62 times.
✓ Branch 1 (6→10) taken 6 times.
|
68 | if (r) { |
708 | 62 | find_unused_subsyntaxes(&e->syntaxes, ebuf); | |
709 | } | ||
710 | } else { | ||
711 | 1 | free_syntax(syn->current_syntax); | |
712 | 1 | syn->current_syntax = NULL; | |
713 | } | ||
714 | } | ||
715 | |||
716 | 70 | ebuf->config_filename = saved_file; | |
717 | 70 | ebuf->config_line = saved_line; | |
718 | |||
719 |
2/2✓ Branch 0 (10→11) taken 63 times.
✓ Branch 1 (10→17) taken 7 times.
|
70 | if (!r) { |
720 | return NULL; | ||
721 | } | ||
722 | |||
723 | 63 | const char *base = path_basename(config_filename); | |
724 | 63 | Syntax *syntax = find_syntax(&e->syntaxes, base); | |
725 |
2/2✓ Branch 0 (12→13) taken 2 times.
✓ Branch 1 (12→15) taken 61 times.
|
63 | if (!syntax) { |
726 | 2 | error_msg ( | |
727 | &e->err, | ||
728 | "%s: no main syntax found (i.e. with name '%s')", | ||
729 | config_filename, | ||
730 | base | ||
731 | ); | ||
732 | 2 | return NULL; | |
733 | } | ||
734 | |||
735 |
1/2✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 61 times.
|
61 | if (e->status != EDITOR_INITIALIZING) { |
736 | ✗ | update_syntax_styles(syntax, &e->styles); | |
737 | } | ||
738 | |||
739 | return syntax; | ||
740 | } | ||
741 | |||
742 | 55 | static Syntax *load_syntax_builtin(EditorState *e, const char *name, SyntaxLoadFlags flags) | |
743 | { | ||
744 | 55 | const BuiltinConfig *cfg = get_builtin_config(name); | |
745 |
1/2✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→6) not taken.
|
55 | if (!cfg) { |
746 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 55 times.
|
55 | if (flags & SYN_MUST_EXIST) { |
747 | ✗ | error_msg(&e->err, "no built-in config with name '%s'", name); | |
748 | } | ||
749 | 55 | return NULL; | |
750 | } | ||
751 | ✗ | return load_syntax(e, cfg->text, name, flags | SYN_BUILTIN); | |
752 | } | ||
753 | |||
754 | 57 | Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags) | |
755 | { | ||
756 | 57 | char *alloc; | |
757 | 57 | ssize_t size = read_file(filename, &alloc, 0); | |
758 |
2/2✓ Branch 0 (3→4) taken 55 times.
✓ Branch 1 (3→10) taken 2 times.
|
57 | if (size < 0) { |
759 | 55 | int err = errno; | |
760 |
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)) { |
761 | ✗ | error_msg(&e->err, "Error reading %s: %s", filename, strerror(err)); | |
762 | ✗ | errno = err; | |
763 | } | ||
764 | 55 | return NULL; | |
765 | } | ||
766 | |||
767 | 2 | StringView config = string_view(alloc, size); | |
768 | 2 | Syntax *syntax = load_syntax(e, config, filename, flags); | |
769 | 2 | free(alloc); | |
770 | |||
771 |
2/2✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 1 times.
|
2 | if (unlikely(!syntax)) { |
772 | 1 | errno = EINVAL; | |
773 | } | ||
774 | |||
775 | return syntax; | ||
776 | } | ||
777 | |||
778 | 55 | Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype) | |
779 | { | ||
780 |
1/2✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→8) not taken.
|
55 | if (!is_valid_filetype_name(filetype)) { |
781 | return NULL; | ||
782 | } | ||
783 | |||
784 | 55 | const char *cfgdir = e->user_config_dir; | |
785 | 55 | char filename[8192]; | |
786 | 55 | xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype); | |
787 | |||
788 | 55 | Syntax *syn = load_syntax_file(e, filename, 0); | |
789 |
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) { |
790 | return syn; | ||
791 | } | ||
792 | |||
793 | // Skip past "%s/" (cfgdir) part of formatted string from above and | ||
794 | // try to load a built-in syntax named `syntax/<filetype>` | ||
795 | 55 | const char *builtin_name = filename + strlen(cfgdir) + STRLEN("/"); | |
796 | 55 | return load_syntax_builtin(e, builtin_name, 0); | |
797 | } | ||
798 |