Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <errno.h> | ||
2 | #include <stdbool.h> | ||
3 | #include <stdlib.h> | ||
4 | #include <string.h> | ||
5 | #include "state.h" | ||
6 | #include "command/args.h" | ||
7 | #include "command/error.h" | ||
8 | #include "command/run.h" | ||
9 | #include "config.h" | ||
10 | #include "editor.h" | ||
11 | #include "filetype.h" | ||
12 | #include "syntax/merge.h" | ||
13 | #include "util/bsearch.h" | ||
14 | #include "util/debug.h" | ||
15 | #include "util/hashset.h" | ||
16 | #include "util/intern.h" | ||
17 | #include "util/log.h" | ||
18 | #include "util/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 | 3901 | static bool in_syntax(const SyntaxLoader *syn, ErrorBuffer *ebuf) | |
26 | { | ||
27 |
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"); |
28 | } | ||
29 | |||
30 | 2810 | static bool in_state(const SyntaxLoader *syn, ErrorBuffer *ebuf) | |
31 | { | ||
32 |
1/2✓ Branch 0 (3→4) taken 2810 times.
✗ Branch 1 (3→9) not taken.
|
2810 | if (unlikely(!in_syntax(syn, ebuf))) { |
33 | return false; | ||
34 | } | ||
35 |
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"); |
36 | } | ||
37 | |||
38 | 1242 | static bool close_state(SyntaxLoader *syn, ErrorBuffer *ebuf) | |
39 | { | ||
40 | 1242 | const State *state = syn->current_state; | |
41 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→5) taken 1241 times.
|
1242 | if (!state) { |
42 | return true; | ||
43 | } | ||
44 | |||
45 | 1 | syn->current_state = NULL; | |
46 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (likely(state->type != STATE_INVALID)) { |
47 | return true; | ||
48 | } | ||
49 | |||
50 | // This error applies to the state itself rather than the last command, so | ||
51 | // it doesn't make sense to include the command name in the error message | ||
52 | 1 | const char *name = state->name; | |
53 | 1 | return error_msg_for_cmd(ebuf, NULL, "No default action in state '%s'", name); | |
54 | } | ||
55 | |||
56 | 2714 | static State *find_or_add_state(const SyntaxLoader *syn, const char *name) | |
57 | { | ||
58 | 2714 | State *state = find_state(syn->current_syntax, name); | |
59 |
2/2✓ Branch 0 (3→4) taken 924 times.
✓ Branch 1 (3→9) taken 1790 times.
|
2714 | if (state) { |
60 | return state; | ||
61 | } | ||
62 | |||
63 | 924 | state = xcalloc1(sizeof(*state)); | |
64 | 924 | state->name = xstrdup(name); | |
65 | 924 | state->defined = false; | |
66 | 924 | state->type = STATE_INVALID; | |
67 | |||
68 |
2/2✓ Branch 0 (6→7) taken 151 times.
✓ Branch 1 (6→8) taken 773 times.
|
924 | if (syn->current_syntax->states.count == 0) { |
69 | 151 | syn->current_syntax->start_state = state; | |
70 | } | ||
71 | |||
72 | 924 | return hashmap_insert(&syn->current_syntax->states, state->name, state); | |
73 | } | ||
74 | |||
75 | 2448 | static State *reference_state ( | |
76 | const SyntaxLoader *syn, | ||
77 | ErrorBuffer *ebuf, | ||
78 | const char *name | ||
79 | ) { | ||
80 |
2/2✓ Branch 0 (2→3) taken 657 times.
✓ Branch 1 (2→4) taken 1791 times.
|
2448 | if (streq(name, "this")) { |
81 | 657 | return syn->current_state; | |
82 | } | ||
83 | |||
84 | 1791 | State *state = find_or_add_state(syn, name); | |
85 |
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)) { |
86 | 1 | error_msg ( | |
87 | ebuf, | ||
88 | "destination '%s' can be optimized to 'this' in '%s' syntax", | ||
89 | name, | ||
90 | 1 | syn->current_syntax->name | |
91 | ); | ||
92 | } | ||
93 | |||
94 | return state; | ||
95 | } | ||
96 | |||
97 | 272 | static bool in_subsyntax(const SyntaxLoader *syn, ErrorBuffer *ebuf) | |
98 | { | ||
99 | 272 | bool ss = likely(is_subsyntax(syn->current_syntax)); | |
100 |
1/4✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 272 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→6) not taken.
|
272 | return ss || error_msg(ebuf, "Destination state 'END' only allowed in a subsyntax"); |
101 | } | ||
102 | |||
103 | 188 | static Syntax *must_find_subsyntax ( | |
104 | const HashMap *syntaxes, | ||
105 | ErrorBuffer *ebuf, | ||
106 | const char *name | ||
107 | ) { | ||
108 | 188 | Syntax *syntax = find_any_syntax(syntaxes, name); | |
109 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 188 times.
|
188 | if (unlikely(!syntax)) { |
110 | ✗ | error_msg(ebuf, "No such syntax '%s'", name); | |
111 | ✗ | return NULL; | |
112 | } | ||
113 |
1/2✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 188 times.
|
188 | if (unlikely(!is_subsyntax(syntax))) { |
114 | ✗ | error_msg(ebuf, "Syntax '%s' is not a subsyntax", name); | |
115 | ✗ | return NULL; | |
116 | } | ||
117 | return syntax; | ||
118 | } | ||
119 | |||
120 | 175 | static bool subsyntax_call ( | |
121 | EditorState *e, | ||
122 | const char *name, | ||
123 | const char *ret, | ||
124 | State **dest | ||
125 | ) { | ||
126 | 175 | Syntax *subsyn = must_find_subsyntax(&e->syntaxes, &e->err, name); | |
127 | |||
128 | 175 | SyntaxMerge m = { | |
129 | .subsyn = subsyn, | ||
130 | .return_state = NULL, | ||
131 | .delim = NULL, | ||
132 | .delim_len = 0, | ||
133 | }; | ||
134 | |||
135 |
2/2✓ Branch 0 (3→4) taken 17 times.
✓ Branch 1 (3→6) taken 158 times.
|
175 | if (streq(ret, "END")) { |
136 |
1/2✓ Branch 0 (5→9) taken 17 times.
✗ Branch 1 (5→12) not taken.
|
17 | if (!in_subsyntax(&e->syn, &e->err)) { |
137 | return false; | ||
138 | } | ||
139 |
1/2✓ Branch 0 (6→7) taken 158 times.
✗ Branch 1 (6→12) not taken.
|
158 | } else if (subsyn) { |
140 | 158 | m.return_state = reference_state(&e->syn, &e->err, ret); | |
141 | } | ||
142 | |||
143 |
1/2✓ Branch 0 (9→10) taken 175 times.
✗ Branch 1 (9→12) not taken.
|
175 | if (subsyn) { |
144 | 175 | *dest = merge_syntax(e->syn.current_syntax, &m, &e->styles); | |
145 | 175 | return true; | |
146 | } | ||
147 | |||
148 | return false; | ||
149 | } | ||
150 | |||
151 | 2720 | static bool destination_state(EditorState *e, const char *name, State **dest) | |
152 | { | ||
153 | 2720 | const char *sep = strchr(name, ':'); | |
154 |
2/2✓ Branch 0 (2→3) taken 175 times.
✓ Branch 1 (2→6) taken 2545 times.
|
2720 | if (sep) { |
155 | // subsyntax:returnstate | ||
156 | 175 | char *sub = xstrcut(name, sep - name); | |
157 | 175 | bool success = subsyntax_call(e, sub, sep + 1, dest); | |
158 | 175 | free(sub); | |
159 | 175 | return success; | |
160 | } | ||
161 | |||
162 |
2/2✓ Branch 0 (6→7) taken 255 times.
✓ Branch 1 (6→10) taken 2290 times.
|
2545 | if (streq(name, "END")) { |
163 |
1/2✓ Branch 0 (8→9) taken 255 times.
✗ Branch 1 (8→12) not taken.
|
255 | if (!in_subsyntax(&e->syn, &e->err)) { |
164 | return false; | ||
165 | } | ||
166 | 255 | *dest = NULL; | |
167 | 255 | return true; | |
168 | } | ||
169 | |||
170 | 2290 | *dest = reference_state(&e->syn, &e->err, name); | |
171 | 2290 | return true; | |
172 | } | ||
173 | |||
174 | 2150 | static void lint_emit_name ( | |
175 | ErrorBuffer *ebuf, | ||
176 | SyntaxLoadFlags flags, | ||
177 | const char *ename, | ||
178 | const State *dest | ||
179 | ) { | ||
180 | 2150 | if ( | |
181 |
2/2✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→7) taken 2136 times.
|
2150 | (flags & SYN_LINT) |
182 | 14 | && ename | |
183 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→7) taken 13 times.
|
14 | && dest |
184 |
1/2✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→7) not taken.
|
1 | && dest->emit_name |
185 |
1/2✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→7) not taken.
|
1 | && interned_strings_equal(ename, dest->emit_name) |
186 | ) { | ||
187 | 1 | error_msg ( | |
188 | ebuf, | ||
189 | "emit-name '%s' not needed (destination state uses same emit-name)", | ||
190 | ename | ||
191 | ); | ||
192 | } | ||
193 | 2150 | } | |
194 | |||
195 | 1888 | static Condition *add_condition ( | |
196 | EditorState *e, | ||
197 | ConditionType type, | ||
198 | const char *dest, | ||
199 | const char *emit | ||
200 | ) { | ||
201 | 1888 | BUG_ON(!dest && cond_type_has_destination(type)); | |
202 |
1/2✓ Branch 0 (6→7) taken 1888 times.
✗ Branch 1 (6→18) not taken.
|
1888 | if (!in_state(&e->syn, &e->err)) { |
203 | return NULL; | ||
204 | } | ||
205 | |||
206 | 1888 | State *d = NULL; | |
207 |
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)) { |
208 | return NULL; | ||
209 | } | ||
210 | |||
211 |
2/2✓ Branch 0 (10→11) taken 548 times.
✓ Branch 1 (10→12) taken 1340 times.
|
1888 | emit = emit ? str_intern(emit) : NULL; |
212 | |||
213 | 1888 | if ( | |
214 | 1888 | type != COND_HEREDOCEND | |
215 |
2/2✓ Branch 0 (12→13) taken 1773 times.
✓ Branch 1 (12→15) taken 115 times.
|
1888 | && type != COND_INLIST |
216 |
1/2✓ Branch 0 (13→14) taken 1773 times.
✗ Branch 1 (13→15) not taken.
|
1773 | && type != COND_INLIST_BUFFER |
217 | ) { | ||
218 | 1773 | lint_emit_name(&e->err, e->syn.flags, emit, d); | |
219 | } | ||
220 | |||
221 | 1888 | Condition *c = xcalloc1(sizeof(*c)); | |
222 | 1888 | c->a.destination = d; | |
223 | 1888 | c->a.emit_name = emit; | |
224 | 1888 | c->type = type; | |
225 | 1888 | ptr_array_append(&e->syn.current_state->conds, c); | |
226 | 1888 | return c; | |
227 | } | ||
228 | |||
229 | 18 | static bool cmd_bufis(EditorState *e, const CommandArgs *a) | |
230 | { | ||
231 | 18 | const char *str = a->args[0]; | |
232 | 18 | const size_t len = strlen(str); | |
233 | 18 | Condition *c; | |
234 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 18 times.
|
18 | if (unlikely(len > ARRAYLEN(c->u.str.buf))) { |
235 | ✗ | return error_msg ( | |
236 | &e->err, | ||
237 | "Maximum length of string is %zu bytes", | ||
238 | ARRAYLEN(c->u.str.buf) | ||
239 | ); | ||
240 | } | ||
241 | |||
242 | 18 | ConditionType type = a->flags[0] == 'i' ? COND_BUFIS_ICASE : COND_BUFIS; | |
243 | 18 | c = add_condition(e, type, a->args[1], a->args[2]); | |
244 |
1/2✓ Branch 0 (5→6) taken 18 times.
✗ Branch 1 (5→7) not taken.
|
18 | if (!c) { |
245 | return false; | ||
246 | } | ||
247 | |||
248 | 18 | memcpy(c->u.str.buf, str, len); | |
249 | 18 | c->u.str.len = len; | |
250 | 18 | return true; | |
251 | } | ||
252 | |||
253 | 1547 | static bool cmd_char(EditorState *e, const CommandArgs *a) | |
254 | { | ||
255 | 1547 | const char *chars = a->args[0]; | |
256 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1547 times.
|
1547 | if (unlikely(chars[0] == '\0')) { |
257 | ✗ | return error_msg(&e->err, "char argument can't be empty"); | |
258 | } | ||
259 | |||
260 | 1547 | bool add_to_buffer = cmdargs_has_flag(a, 'b'); | |
261 | 1547 | bool invert = cmdargs_has_flag(a, 'n'); | |
262 | 1547 | ConditionType type; | |
263 |
2/2✓ Branch 0 (6→7) taken 1312 times.
✓ Branch 1 (6→10) taken 235 times.
|
1547 | if (add_to_buffer) { |
264 | type = COND_CHAR_BUFFER; | ||
265 |
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') { |
266 | type = COND_CHAR1; | ||
267 | } else { | ||
268 | 565 | type = COND_CHAR; | |
269 | } | ||
270 | |||
271 | 1547 | Condition *c = add_condition(e, type, a->args[1], a->args[2]); | |
272 |
1/2✓ Branch 0 (11→12) taken 1547 times.
✗ Branch 1 (11→17) not taken.
|
1547 | if (!c) { |
273 | return false; | ||
274 | } | ||
275 | |||
276 |
2/2✓ Branch 0 (12→13) taken 747 times.
✓ Branch 1 (12→14) taken 800 times.
|
1547 | if (type == COND_CHAR1) { |
277 | 747 | c->u.ch = (unsigned char)chars[0]; | |
278 | } else { | ||
279 | 800 | bitset_add_char_range(c->u.bitset, chars); | |
280 |
2/2✓ Branch 0 (15→16) taken 44 times.
✓ Branch 1 (15→17) taken 756 times.
|
800 | if (invert) { |
281 | 44 | BITSET_INVERT(c->u.bitset); | |
282 | } | ||
283 | } | ||
284 | |||
285 | return true; | ||
286 | } | ||
287 | |||
288 | 66 | static bool cmd_default(EditorState *e, const CommandArgs *a) | |
289 | { | ||
290 | 66 | SyntaxLoader *syn = &e->syn; | |
291 | 66 | ErrorBuffer *ebuf = &e->err; | |
292 |
2/4✓ Branch 0 (3→4) taken 66 times.
✗ Branch 1 (3→6) not taken.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 66 times.
|
66 | if (!close_state(syn, ebuf) || !in_syntax(syn, ebuf)) { |
293 | ✗ | return false; | |
294 | } | ||
295 | |||
296 | 66 | const char *value = str_intern(a->args[0]); | |
297 | 66 | HashMap *map = &syn->current_syntax->default_styles; | |
298 |
2/2✓ Branch 0 (14→9) taken 78 times.
✓ Branch 1 (14→15) taken 66 times.
|
144 | for (size_t i = 1, n = a->nr_args; i < n; i++) { |
299 | 78 | const char *name = a->args[i]; | |
300 | 78 | const void *oldval = hashmap_insert_or_replace(map, xstrdup(name), (char*)value); | |
301 |
1/2✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 78 times.
|
78 | if (unlikely(oldval)) { |
302 | ✗ | 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 3607 times.
✓ Branch 1 (18→19) taken 102 times.
|
3709 | for (size_t i = 1, n = a->nr_args; i < n; i++) { |
408 | 3607 | const char *str = args[i]; | |
409 | 3607 | 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 | ✗ | error_msg(ebuf, "Redundant emit-name '%s'", emit_name); | |
545 | } | ||
546 | |||
547 | 923 | state->defined = true; | |
548 |
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); |
549 | 923 | syn->current_state = state; | |
550 | 923 | return true; | |
551 | } | ||
552 | |||
553 | 118 | static bool cmd_str(EditorState *e, const CommandArgs *a) | |
554 | { | ||
555 | 118 | const char *str = a->args[0]; | |
556 | 118 | size_t len = strlen(str); | |
557 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 118 times.
|
118 | if (unlikely(len < 2)) { |
558 | ✗ | return error_msg(&e->err, "string should be at least 2 bytes; use 'char' for single bytes"); | |
559 | } | ||
560 | |||
561 | 118 | Condition *c; | |
562 | 118 | size_t maxlen = ARRAYLEN(c->u.str.buf); | |
563 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 118 times.
|
118 | if (unlikely(len > maxlen)) { |
564 | ✗ | return error_msg(&e->err, "maximum length of string is %zu bytes", maxlen); | |
565 | } | ||
566 | |||
567 | 118 | ConditionType type; | |
568 |
2/2✓ Branch 0 (7→8) taken 111 times.
✓ Branch 1 (7→10) taken 7 times.
|
118 | if (cmdargs_has_flag(a, 'i')) { |
569 | type = COND_STR_ICASE; | ||
570 | } else { | ||
571 |
2/2✓ Branch 0 (8→9) taken 36 times.
✓ Branch 1 (8→10) taken 75 times.
|
111 | type = (len == 2) ? COND_STR2 : COND_STR; |
572 | } | ||
573 | |||
574 | 118 | c = add_condition(e, type, a->args[1], a->args[2]); | |
575 |
1/2✓ Branch 0 (11→12) taken 118 times.
✗ Branch 1 (11→13) not taken.
|
118 | if (!c) { |
576 | return false; | ||
577 | } | ||
578 | |||
579 | 118 | memcpy(c->u.str.buf, str, len); | |
580 | 118 | c->u.str.len = len; | |
581 | 118 | return true; | |
582 | } | ||
583 | |||
584 | 151 | static bool finish_syntax(SyntaxLoader *syn, ErrorBuffer *ebuf, HashMap *syntaxes) | |
585 | { | ||
586 | 151 | Syntax *syntax = syn->current_syntax; | |
587 | 151 | BUG_ON(!syntax); | |
588 |
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); |
589 | 6 | if (!r) { | |
590 | 6 | free_syntax(syntax); | |
591 | } | ||
592 | 151 | syn->current_syntax = NULL; | |
593 | 151 | return r; | |
594 | } | ||
595 | |||
596 | 152 | static bool cmd_syntax(EditorState *e, const CommandArgs *a) | |
597 | { | ||
598 | 152 | SyntaxLoader *syn = &e->syn; | |
599 |
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)) { |
600 | return false; | ||
601 | } | ||
602 | |||
603 | 152 | Syntax *syntax = xcalloc1(sizeof(*syntax)); | |
604 | 152 | syntax->name = xstrdup(a->args[0]); | |
605 |
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)) { |
606 | 7 | syntax->warned_unused_subsyntax = true; | |
607 | } | ||
608 | |||
609 | 152 | syn->current_syntax = syntax; | |
610 | 152 | syn->current_state = NULL; | |
611 | 152 | return true; | |
612 | } | ||
613 | |||
614 | #define CMD(name, flags, min, max, func) \ | ||
615 | {name, flags, CMDOPT_ALLOW_IN_RC, min, max, func} | ||
616 | |||
617 | static const Command cmds[] = { | ||
618 | CMD("bufis", "i", 2, 3, cmd_bufis), | ||
619 | CMD("char", "bn", 2, 3, cmd_char), | ||
620 | CMD("default", "", 2, -1, cmd_default), | ||
621 | CMD("eat", "", 1, 2, cmd_eat), | ||
622 | CMD("heredocbegin", "", 2, 2, cmd_heredocbegin), | ||
623 | CMD("heredocend", "", 1, 2, cmd_heredocend), | ||
624 | CMD("include", "b", 1, 1, cmd_include), | ||
625 | CMD("inlist", "b", 2, 3, cmd_inlist), | ||
626 | CMD("list", "i", 2, -1, cmd_list), | ||
627 | CMD("noeat", "b", 1, 1, cmd_noeat), | ||
628 | CMD("recolor", "", 1, 2, cmd_recolor), | ||
629 | CMD("require", "f", 1, 1, cmd_require), | ||
630 | CMD("state", "", 1, 2, cmd_state), | ||
631 | CMD("str", "i", 2, 3, cmd_str), | ||
632 | CMD("syntax", "", 1, 1, cmd_syntax), | ||
633 | }; | ||
634 | |||
635 | 24 | UNITTEST { | |
636 | 24 | CHECK_BSEARCH_ARRAY(cmds, name); | |
637 | 24 | } | |
638 | |||
639 | 4070 | static const Command *find_syntax_command(const char *name) | |
640 | { | ||
641 | 4070 | return BSEARCH(name, cmds, command_cmp); | |
642 | } | ||
643 | |||
644 | ✗ | static char *expand_syntax_var(const EditorState *e, const char *name) | |
645 | { | ||
646 | ✗ | if (streq(name, "DTE_HOME")) { | |
647 | ✗ | return xstrdup(e->user_config_dir); | |
648 | } | ||
649 | return NULL; | ||
650 | } | ||
651 | |||
652 | static const CommandSet syntax_commands = { | ||
653 | .lookup = find_syntax_command, | ||
654 | }; | ||
655 | |||
656 | 75 | static CommandRunner cmdrunner_for_syntaxes(EditorState *e) | |
657 | { | ||
658 | 75 | CommandRunner runner = cmdrunner(e, &syntax_commands); | |
659 | 75 | runner.expand_variable = expand_syntax_var; | |
660 | 75 | runner.flags |= CMDRUNNER_STOP_AT_FIRST_ERROR; | |
661 | 75 | return runner; | |
662 | } | ||
663 | |||
664 | 5 | static ConfigFlags syn_flags_to_cfg_flags(SyntaxLoadFlags flags) | |
665 | { | ||
666 | 5 | static_assert(SYN_MUST_EXIST == (SyntaxLoadFlags)CFG_MUST_EXIST); | |
667 | 5 | static_assert(SYN_BUILTIN == (SyntaxLoadFlags)CFG_BUILTIN); | |
668 | 5 | SyntaxLoadFlags mask = SYN_MUST_EXIST | SYN_BUILTIN; | |
669 | 5 | return (ConfigFlags)(flags & mask); | |
670 | } | ||
671 | |||
672 | 5 | static int read_syntax(EditorState *e, const char *filename, SyntaxLoadFlags flags) | |
673 | { | ||
674 | 5 | CommandRunner runner = cmdrunner_for_syntaxes(e); | |
675 | 5 | return read_config(&runner, filename, syn_flags_to_cfg_flags(flags)); | |
676 | } | ||
677 | |||
678 | 70 | Syntax *load_syntax ( | |
679 | EditorState *e, | ||
680 | StringView config_text, | ||
681 | const char *config_filename, | ||
682 | SyntaxLoadFlags flags | ||
683 | ) { | ||
684 | 70 | SyntaxLoader *syn = &e->syn; | |
685 | 70 | *syn = (SyntaxLoader) { | |
686 | .current_syntax = NULL, | ||
687 | .current_state = NULL, | ||
688 | 70 | .flags = flags | SYN_WARN_ON_UNUSED_SUBSYN, | |
689 | }; | ||
690 | |||
691 | 70 | ErrorBuffer *ebuf = &e->err; | |
692 | 70 | const char *saved_file = ebuf->config_filename; | |
693 | 70 | const unsigned int saved_line = ebuf->config_line; | |
694 | 70 | CommandRunner runner = cmdrunner_for_syntaxes(e); | |
695 | |||
696 | 70 | ebuf->config_filename = config_filename; | |
697 | 70 | ebuf->config_line = 1; | |
698 | 70 | bool r = exec_config(&runner, config_text); | |
699 | |||
700 |
2/2✓ Branch 0 (3→4) taken 69 times.
✓ Branch 1 (3→10) taken 1 times.
|
70 | if (syn->current_syntax) { |
701 |
2/2✓ Branch 0 (4→5) taken 68 times.
✓ Branch 1 (4→8) taken 1 times.
|
69 | if (r) { |
702 | 68 | r = finish_syntax(syn, ebuf, &e->syntaxes); | |
703 |
2/2✓ Branch 0 (6→7) taken 62 times.
✓ Branch 1 (6→10) taken 6 times.
|
68 | if (r) { |
704 | 62 | find_unused_subsyntaxes(&e->syntaxes, ebuf); | |
705 | } | ||
706 | } else { | ||
707 | 1 | free_syntax(syn->current_syntax); | |
708 | 1 | syn->current_syntax = NULL; | |
709 | } | ||
710 | } | ||
711 | |||
712 | 70 | ebuf->config_filename = saved_file; | |
713 | 70 | ebuf->config_line = saved_line; | |
714 | |||
715 |
2/2✓ Branch 0 (10→11) taken 63 times.
✓ Branch 1 (10→17) taken 7 times.
|
70 | if (!r) { |
716 | return NULL; | ||
717 | } | ||
718 | |||
719 | 63 | const char *base = path_basename(config_filename); | |
720 | 63 | Syntax *syntax = find_syntax(&e->syntaxes, base); | |
721 |
2/2✓ Branch 0 (12→13) taken 2 times.
✓ Branch 1 (12→15) taken 61 times.
|
63 | if (!syntax) { |
722 | 2 | error_msg(&e->err, "%s: no main syntax found (i.e. with name '%s')", config_filename, base); | |
723 | 2 | return NULL; | |
724 | } | ||
725 | |||
726 |
1/2✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→17) taken 61 times.
|
61 | if (e->status != EDITOR_INITIALIZING) { |
727 | ✗ | update_syntax_styles(syntax, &e->styles); | |
728 | } | ||
729 | |||
730 | return syntax; | ||
731 | } | ||
732 | |||
733 | 55 | static Syntax *load_syntax_builtin(EditorState *e, const char *name, SyntaxLoadFlags flags) | |
734 | { | ||
735 | 55 | const BuiltinConfig *cfg = get_builtin_config(name); | |
736 |
1/2✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→6) not taken.
|
55 | if (!cfg) { |
737 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 55 times.
|
55 | if (flags & SYN_MUST_EXIST) { |
738 | ✗ | error_msg(&e->err, "no built-in config with name '%s'", name); | |
739 | } | ||
740 | 55 | return NULL; | |
741 | } | ||
742 | ✗ | return load_syntax(e, cfg->text, name, flags | SYN_BUILTIN); | |
743 | } | ||
744 | |||
745 | 57 | Syntax *load_syntax_file(EditorState *e, const char *filename, SyntaxLoadFlags flags) | |
746 | { | ||
747 | 57 | char *alloc; | |
748 | 57 | ssize_t size = read_file(filename, &alloc, 0); | |
749 |
2/2✓ Branch 0 (3→4) taken 55 times.
✓ Branch 1 (3→10) taken 2 times.
|
57 | if (size < 0) { |
750 | 55 | int err = errno; | |
751 |
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)) { |
752 | ✗ | error_msg(&e->err, "Error reading %s: %s", filename, strerror(err)); | |
753 | ✗ | errno = err; | |
754 | } | ||
755 | 55 | return NULL; | |
756 | } | ||
757 | |||
758 | 2 | StringView config = string_view(alloc, size); | |
759 | 2 | Syntax *syntax = load_syntax(e, config, filename, flags); | |
760 | 2 | free(alloc); | |
761 | |||
762 |
2/2✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 1 times.
|
2 | if (unlikely(!syntax)) { |
763 | 1 | errno = EINVAL; | |
764 | } | ||
765 | |||
766 | return syntax; | ||
767 | } | ||
768 | |||
769 | 55 | Syntax *load_syntax_by_filetype(EditorState *e, const char *filetype) | |
770 | { | ||
771 |
1/2✓ Branch 0 (2→3) taken 55 times.
✗ Branch 1 (2→8) not taken.
|
55 | if (!is_valid_filetype_name(filetype)) { |
772 | return NULL; | ||
773 | } | ||
774 | |||
775 | 55 | const char *cfgdir = e->user_config_dir; | |
776 | 55 | char filename[8192]; | |
777 | 55 | xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype); | |
778 | |||
779 | 55 | Syntax *syn = load_syntax_file(e, filename, 0); | |
780 |
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) { |
781 | return syn; | ||
782 | } | ||
783 | |||
784 | // Skip past "%s/" (cfgdir) part of formatted string from above and | ||
785 | // try to load a built-in syntax named `syntax/<filetype>` | ||
786 | 55 | const char *builtin_name = filename + strlen(cfgdir) + STRLEN("/"); | |
787 | 55 | return load_syntax_builtin(e, builtin_name, 0); | |
788 | } | ||
789 |