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