dte test coverage


Directory: ./
File: src/syntax/syntax.c
Date: 2025-07-01 18:22:46
Exec Total Coverage
Lines: 109 135 80.7%
Functions: 20 22 90.9%
Branches: 58 70 82.9%

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