dte test coverage


Directory: ./
File: src/syntax/syntax.c
Date: 2025-05-08 15:05:54
Exec Total Coverage
Lines: 95 137 69.3%
Functions: 19 22 86.4%
Branches: 49 72 68.1%

Line Branch Exec Source
1 #include <stdlib.h>
2 #include <string.h>
3 #include "syntax.h"
4 #include "util/str-util.h"
5 #include "util/xmalloc.h"
6 #include "util/xsnprintf.h"
7
8 199 StringList *find_string_list(const Syntax *syn, const char *name)
9 {
10 199 return hashmap_get(&syn->string_lists, name);
11 }
12
13 5486 State *find_state(const Syntax *syn, const char *name)
14 {
15 5486 return hashmap_get(&syn->states, name);
16 }
17
18 596 Syntax *find_any_syntax(const HashMap *syntaxes, const char *name)
19 {
20 596 return hashmap_get(syntaxes, name);
21 }
22
23 // Each State is visited at most once (when reachable) and recursion
24 // is bounded by the longest chain of `Action::destination` pointers
25 // (typically not more than 20 or so)
26 // NOLINTNEXTLINE(misc-no-recursion)
27 6037 static void visit(State *s)
28 {
29
2/2
✓ Branch 0 (2→3) taken 2300 times.
✓ Branch 1 (2→10) taken 3737 times.
6037 if (s->visited) {
30 return;
31 }
32 2300 s->visited = true;
33
2/2
✓ Branch 0 (7→4) taken 4086 times.
✓ Branch 1 (7→8) taken 2300 times.
6386 for (size_t i = 0, n = s->conds.count; i < n; i++) {
34 4086 const Condition *cond = s->conds.ptrs[i];
35
2/2
✓ Branch 0 (4→5) taken 3762 times.
✓ Branch 1 (4→6) taken 324 times.
4086 if (cond->a.destination) {
36 3762 visit(cond->a.destination);
37 }
38 }
39
2/2
✓ Branch 0 (8→9) taken 2140 times.
✓ Branch 1 (8→10) taken 160 times.
2300 if (s->default_action.destination) {
40 2140 visit(s->default_action.destination);
41 }
42 }
43
44 4086 static void free_condition(Condition *cond)
45 {
46 4086 free(cond);
47 4086 }
48
49 static void free_heredoc_state(HeredocState *s)
50 {
51 free(s);
52 }
53
54 2300 static void free_state(State *s)
55 {
56 2300 ptr_array_free_cb(&s->conds, FREE_FUNC(free_condition));
57 2300 ptr_array_free_cb(&s->heredoc.states, FREE_FUNC(free_heredoc_state));
58 2300 free(s);
59 2300 }
60
61 98 static void free_string_list(StringList *list)
62 {
63 98 hashset_free(&list->strings);
64 98 free(list);
65 98 }
66
67 135 static void free_syntax_contents(Syntax *syn)
68 {
69 135 hashmap_free(&syn->states, FREE_FUNC(free_state));
70 135 hashmap_free(&syn->string_lists, FREE_FUNC(free_string_list));
71 135 hashmap_free(&syn->default_styles, NULL);
72 135 }
73
74 static void free_syntax(Syntax *syn)
75 {
76 free_syntax_contents(syn);
77 free(syn->name);
78 free(syn);
79 }
80
81 135 static void free_syntax_cb(Syntax *syn)
82 {
83 135 free_syntax_contents(syn);
84 135 free(syn);
85 135 }
86
87 9 void free_syntaxes(HashMap *syntaxes)
88 {
89 9 hashmap_free(syntaxes, FREE_FUNC(free_syntax_cb));
90 9 }
91
92 135 void finalize_syntax (
93 HashMap *syntaxes,
94 Syntax *syn,
95 ErrorBuffer *ebuf,
96 unsigned int saved_nr_errors
97 ) {
98
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 135 times.
135 if (syn->states.count == 0) {
99 error_msg(ebuf, "Empty syntax");
100 }
101
102
2/2
✓ Branch 0 (9→5) taken 2300 times.
✓ Branch 1 (9→10) taken 135 times.
2570 for (HashMapIter it = hashmap_iter(&syn->states); hashmap_next(&it); ) {
103 2300 const State *s = it.entry->value;
104
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→8) taken 2300 times.
2300 if (!s->defined) {
105 // This state has been referenced but not defined
106 error_msg(ebuf, "No such state %s", it.entry->key);
107 }
108 }
109
2/2
✓ Branch 0 (15→11) taken 98 times.
✓ Branch 1 (15→16) taken 135 times.
368 for (HashMapIter it = hashmap_iter(&syn->string_lists); hashmap_next(&it); ) {
110 98 const StringList *list = it.entry->value;
111
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→14) taken 98 times.
98 if (!list->defined) {
112 error_msg(ebuf, "No such list %s", it.entry->key);
113 }
114 }
115
116
3/4
✓ Branch 0 (16→17) taken 9 times.
✓ Branch 1 (16→19) taken 126 times.
✗ Branch 2 (17→18) not taken.
✓ Branch 3 (17→19) taken 9 times.
135 if (syn->heredoc && !is_subsyntax(syn)) {
117 error_msg(ebuf, "heredocend can be used only in subsyntaxes");
118 }
119
120
1/2
✗ Branch 0 (20→21) not taken.
✓ Branch 1 (20→22) taken 135 times.
135 if (find_any_syntax(syntaxes, syn->name)) {
121 error_msg(ebuf, "Syntax %s already exists", syn->name);
122 }
123
124
1/2
✗ Branch 0 (22→23) not taken.
✓ Branch 1 (22→25) taken 135 times.
135 if (ebuf->nr_errors != saved_nr_errors) {
125 free_syntax(syn);
126 return;
127 }
128
129 // Unused states and lists cause warnings only (to make loading WIP
130 // syntax files less annoying)
131 135 visit(syn->start_state);
132
2/2
✓ Branch 0 (32→27) taken 2300 times.
✓ Branch 1 (32→33) taken 135 times.
2435 for (HashMapIter it = hashmap_iter(&syn->states); hashmap_next(&it); ) {
133 2300 const State *s = it.entry->value;
134
1/4
✗ Branch 0 (27→28) not taken.
✓ Branch 1 (27→30) taken 2300 times.
✗ Branch 2 (28→29) not taken.
✗ Branch 3 (28→30) not taken.
2300 if (!s->visited && !s->copied) {
135 error_msg(ebuf, "State %s is unreachable", it.entry->key);
136 }
137 }
138
2/2
✓ Branch 0 (38→34) taken 98 times.
✓ Branch 1 (38→39) taken 135 times.
368 for (HashMapIter it = hashmap_iter(&syn->string_lists); hashmap_next(&it); ) {
139 98 const StringList *list = it.entry->value;
140
1/2
✗ Branch 0 (34→35) not taken.
✓ Branch 1 (34→37) taken 98 times.
98 if (!list->used) {
141 error_msg(ebuf, "List %s never used", it.entry->key);
142 }
143 }
144
145 135 hashmap_insert(syntaxes, syn->name, syn);
146 }
147
148 275 Syntax *find_syntax(const HashMap *syntaxes, const char *name)
149 {
150 275 Syntax *syn = find_any_syntax(syntaxes, name);
151
3/4
✓ Branch 0 (3→4) taken 114 times.
✓ Branch 1 (3→6) taken 161 times.
✗ Branch 2 (4→5) not taken.
✓ Branch 3 (4→6) taken 114 times.
275 if (syn && is_subsyntax(syn)) {
152 return NULL;
153 }
154 return syn;
155 }
156
157 633 static const char *find_default_style(const Syntax *syn, const char *name)
158 {
159 633 return hashmap_get(&syn->default_styles, name);
160 }
161
162 4209 static const char *get_effective_emit_name(const Action *a)
163 {
164
2/2
✓ Branch 0 (2→3) taken 3062 times.
✓ Branch 1 (2→4) taken 1147 times.
4209 return a->emit_name ? a->emit_name : a->destination->emit_name;
165 }
166
167 4209 static void update_action_style(const Syntax *syn, Action *a, const StyleMap *styles)
168 {
169 4209 const char *name = get_effective_emit_name(a);
170 4209 char full[256];
171 4209 xsnprintf(full, sizeof full, "%s.%s", syn->name, name);
172 4209 a->emit_style = find_style(styles, full);
173
2/2
✓ Branch 0 (4→5) taken 633 times.
✓ Branch 1 (4→10) taken 3576 times.
4209 if (a->emit_style) {
174 3894 return;
175 }
176
177 633 const char *def = find_default_style(syn, name);
178
2/2
✓ Branch 0 (6→7) taken 315 times.
✓ Branch 1 (6→10) taken 318 times.
633 if (!def) {
179 return;
180 }
181
182 315 xsnprintf(full, sizeof full, "%s.%s", syn->name, def);
183 315 a->emit_style = find_style(styles, full);
184 }
185
186 1471 void update_state_styles(const Syntax *syn, State *s, const StyleMap *styles)
187 {
188
2/2
✓ Branch 0 (5→3) taken 2738 times.
✓ Branch 1 (5→6) taken 1471 times.
4209 for (size_t i = 0, n = s->conds.count; i < n; i++) {
189 2738 Condition *c = s->conds.ptrs[i];
190 2738 update_action_style(syn, &c->a, styles);
191 }
192 1471 update_action_style(syn, &s->default_action, styles);
193 1471 }
194
195 132 void update_syntax_styles(Syntax *syn, const StyleMap *styles)
196 {
197
2/2
✓ Branch 0 (2→3) taken 54 times.
✓ Branch 1 (2→8) taken 78 times.
132 if (is_subsyntax(syn)) {
198 // No point in updating styles of a sub-syntax
199 return;
200 }
201
2/2
✓ Branch 0 (6→4) taken 1471 times.
✓ Branch 1 (6→7) taken 54 times.
1525 for (HashMapIter it = hashmap_iter(&syn->states); hashmap_next(&it); ) {
202 1471 update_state_styles(syn, it.entry->value, styles);
203 }
204 }
205
206 8 void update_all_syntax_styles(const HashMap *syntaxes, const StyleMap *styles)
207 {
208
2/2
✓ Branch 0 (5→3) taken 132 times.
✓ Branch 1 (5→6) taken 8 times.
140 for (HashMapIter it = hashmap_iter(syntaxes); hashmap_next(&it); ) {
209 132 update_syntax_styles(it.entry->value, styles);
210 }
211 8 }
212
213 55 void find_unused_subsyntaxes(const HashMap *syntaxes, ErrorBuffer *ebuf)
214 {
215
2/2
✓ Branch 0 (10→3) taken 3548 times.
✓ Branch 1 (10→11) taken 55 times.
3603 for (HashMapIter it = hashmap_iter(syntaxes); hashmap_next(&it); ) {
216 3548 Syntax *s = it.entry->value;
217
4/6
✓ Branch 0 (3→4) taken 1486 times.
✓ Branch 1 (3→8) taken 2062 times.
✓ Branch 2 (4→5) taken 1486 times.
✗ Branch 3 (4→8) not taken.
✗ Branch 4 (5→6) not taken.
✓ Branch 5 (5→8) taken 1486 times.
3548 if (!s->used && !s->warned_unused_subsyntax && is_subsyntax(s)) {
218 error_msg(ebuf, "Subsyntax %s is unused", s->name);
219 // Don't complain multiple times about the same unused subsyntaxes
220 s->warned_unused_subsyntax = true;
221 }
222 }
223 55 }
224
225 void collect_syntax_emit_names (
226 const Syntax *syntax,
227 PointerArray *a,
228 const char *prefix
229 ) {
230 size_t prefix_len = strlen(prefix);
231 HashSet set;
232 hashset_init(&set, 16, false);
233
234 // Insert all `Action::emit_name` strings beginning with `prefix` into
235 // a HashSet (to avoid duplicates)
236 for (HashMapIter it = hashmap_iter(&syntax->states); hashmap_next(&it); ) {
237 const State *s = it.entry->value;
238 const char *emit = get_effective_emit_name(&s->default_action);
239 if (str_has_strn_prefix(emit, prefix, prefix_len)) {
240 hashset_insert(&set, emit, strlen(emit));
241 }
242 for (size_t i = 0, n = s->conds.count; i < n; i++) {
243 const Condition *cond = s->conds.ptrs[i];
244 emit = get_effective_emit_name(&cond->a);
245 if (str_has_strn_prefix(emit, prefix, prefix_len)) {
246 hashset_insert(&set, emit, strlen(emit));
247 }
248 }
249 }
250
251 const char *ft = syntax->name;
252 size_t ftlen = strlen(ft);
253
254 // Append the collected strings to the PointerArray
255 for (HashSetIter iter = hashset_iter(&set); hashset_next(&iter); ) {
256 const HashSetEntry *h = iter.entry;
257 char *str = xmemjoin3(ft, ftlen, STRN("."), h->str, h->str_len + 1);
258 ptr_array_append(a, str);
259 }
260
261 hashset_free(&set);
262 }
263