src/ctags.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <stdlib.h> | ||
| 2 | #include "ctags.h" | ||
| 3 | #include "util/arith.h" | ||
| 4 | #include "util/ascii.h" | ||
| 5 | #include "util/debug.h" | ||
| 6 | #include "util/str-util.h" | ||
| 7 | #include "util/strtonum.h" | ||
| 8 | #include "util/xmalloc.h" | ||
| 9 | #include "util/xstring.h" | ||
| 10 | |||
| 11 | // Convert an ex(1) style pattern from a tags(5) file to a basic POSIX | ||
| 12 | // regex ("BRE"), so that it can be compiled with regcomp(3) | ||
| 13 | 19 | static size_t regex_from_ex_pattern(StringView ex, char **regex_str) | |
| 14 | { | ||
| 15 | 19 | BUG_ON(ex.length == 0); | |
| 16 | 19 | const char open_delim = ex.data[0]; | |
| 17 | 19 | BUG_ON(open_delim != '/' && open_delim != '?'); | |
| 18 | 19 | char *buf = xmalloc(xmul(2, ex.length)); | |
| 19 | |||
| 20 | // The pattern isn't a real regex; special chars need to be escaped | ||
| 21 |
2/2✓ Branch 20 → 9 taken 890 times.
✓ Branch 20 → 21 taken 1 time.
|
910 | for (size_t i = 1, j = 0; i < ex.length; i++) { |
| 22 | 890 | char c = ex.data[i]; | |
| 23 |
2/2✓ Branch 9 → 10 taken 889 times.
✓ Branch 9 → 21 taken 1 time.
|
890 | if (c == '\0') { |
| 24 | break; | ||
| 25 |
2/2✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 14 taken 886 times.
|
889 | } else if (c == '\\') { |
| 26 |
1/2✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 21 not taken.
|
3 | if (unlikely(++i >= ex.length)) { |
| 27 | break; | ||
| 28 | } | ||
| 29 | 3 | c = ex.data[i]; | |
| 30 |
2/2✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 19 taken 2 times.
|
3 | if (c == '\\') { |
| 31 | // Escape "\\" as "\\" (any other "\x" becomes just "x") | ||
| 32 | 1 | buf[j++] = '\\'; | |
| 33 | } | ||
| 34 |
4/4✓ Branch 14 → 15 taken 859 times.
✓ Branch 14 → 16 taken 27 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 858 times.
|
886 | } else if (c == '*' || c == '[' || c == ']') { |
| 35 | 28 | buf[j++] = '\\'; | |
| 36 |
2/2✓ Branch 17 → 18 taken 17 times.
✓ Branch 17 → 19 taken 841 times.
|
858 | } else if (c == open_delim) { |
| 37 | 17 | buf[j] = '\0'; | |
| 38 | 17 | *regex_str = buf; | |
| 39 | 17 | return i + 1; | |
| 40 | } | ||
| 41 | 872 | buf[j++] = c; | |
| 42 | } | ||
| 43 | |||
| 44 | // End of string reached without a matching end delimiter; invalid input | ||
| 45 | 2 | free(buf); | |
| 46 | 2 | return 0; | |
| 47 | } | ||
| 48 | |||
| 49 | 21 | static size_t parse_ex_cmd(Tag *tag, StringView cmd) | |
| 50 | { | ||
| 51 |
1/2✓ Branch 2 → 3 taken 21 times.
✗ Branch 2 → 13 not taken.
|
21 | if (unlikely(cmd.length == 0)) { |
| 52 | return 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | 21 | size_t n; | |
| 56 |
2/2✓ Branch 4 → 5 taken 19 times.
✓ Branch 4 → 6 taken 2 times.
|
21 | if (strview_has_either_prefix(cmd, "/", "?")) { |
| 57 | 19 | n = regex_from_ex_pattern(cmd, &tag->pattern); | |
| 58 | } else { | ||
| 59 | 2 | n = buf_parse_ulong(cmd, &tag->lineno); | |
| 60 | } | ||
| 61 | |||
| 62 |
2/2✓ Branch 7 → 8 taken 19 times.
✓ Branch 7 → 13 taken 2 times.
|
21 | if (n == 0) { |
| 63 | return 0; | ||
| 64 | } | ||
| 65 | |||
| 66 | 19 | strview_remove_prefix(&cmd, n); | |
| 67 | 19 | StringView delim = strview(";\""); | |
| 68 | 19 | bool trailing_comment = strview_has_sv_prefix(cmd, delim); | |
| 69 |
2/2✓ Branch 10 → 11 taken 16 times.
✓ Branch 10 → 12 taken 3 times.
|
19 | return n + (trailing_comment ? delim.length : 0); |
| 70 | } | ||
| 71 | |||
| 72 | 21 | bool parse_ctags_line(Tag *tag, StringView line) | |
| 73 | { | ||
| 74 | 21 | size_t pos = 0; | |
| 75 | 21 | *tag = (Tag){.name = get_delim(line.data, &pos, line.length, '\t')}; | |
| 76 |
2/4✓ Branch 3 → 4 taken 21 times.
✗ Branch 3 → 26 not taken.
✓ Branch 4 → 5 taken 21 times.
✗ Branch 4 → 26 not taken.
|
21 | if (tag->name.length == 0 || pos >= line.length) { |
| 77 | return false; | ||
| 78 | } | ||
| 79 | |||
| 80 | 21 | tag->filename = get_delim(line.data, &pos, line.length, '\t'); | |
| 81 |
2/4✓ Branch 6 → 7 taken 21 times.
✗ Branch 6 → 26 not taken.
✓ Branch 7 → 8 taken 21 times.
✗ Branch 7 → 26 not taken.
|
21 | if (tag->filename.length == 0 || pos >= line.length) { |
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | 21 | size_t len = parse_ex_cmd(tag, strview_from_slice(line.data, pos, line.length)); | |
| 86 |
2/2✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 13 taken 19 times.
|
21 | if (len == 0) { |
| 87 | 2 | BUG_ON(tag->pattern); | |
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | 19 | pos += len; | |
| 92 |
2/2✓ Branch 13 → 14 taken 18 times.
✓ Branch 13 → 26 taken 1 time.
|
19 | if (pos >= line.length) { |
| 93 | return true; | ||
| 94 | } | ||
| 95 | |||
| 96 | /* | ||
| 97 | * Extension fields (key:[value]): | ||
| 98 | * | ||
| 99 | * file: visibility limited to this file | ||
| 100 | * struct:NAME tag is member of struct NAME | ||
| 101 | * union:NAME tag is member of union NAME | ||
| 102 | * typeref:struct:NAME::MEMBER_TYPE MEMBER_TYPE is type of the tag | ||
| 103 | */ | ||
| 104 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 25 taken 17 times.
|
18 | if (line.data[pos++] != '\t') { |
| 105 | // free `pattern` allocated by parse_ex_cmd() | ||
| 106 | 1 | free_tag(tag); | |
| 107 | 1 | tag->pattern = NULL; | |
| 108 | 1 | return false; | |
| 109 | } | ||
| 110 | |||
| 111 |
2/2✓ Branch 25 → 17 taken 38 times.
✓ Branch 25 → 26 taken 17 times.
|
55 | while (pos < line.length) { |
| 112 | 38 | StringView field = get_delim(line.data, &pos, line.length, '\t'); | |
| 113 |
3/4✓ Branch 18 → 19 taken 17 times.
✓ Branch 18 → 21 taken 21 times.
✓ Branch 19 → 20 taken 17 times.
✗ Branch 19 → 21 not taken.
|
38 | if (field.length == 1 && ascii_isalpha(field.data[0])) { |
| 114 | 17 | tag->kind = field.data[0]; | |
| 115 |
2/2✓ Branch 22 → 23 taken 7 times.
✓ Branch 22 → 24 taken 14 times.
|
21 | } else if (strview_equal_cstring(field, "file:")) { |
| 116 | 7 | tag->local = true; | |
| 117 | } | ||
| 118 | // TODO: struct/union/typeref | ||
| 119 | } | ||
| 120 | |||
| 121 | return true; | ||
| 122 | } | ||
| 123 | |||
| 124 | 18 | bool next_tag ( | |
| 125 | StringView text, // Tag file contents | ||
| 126 | size_t *posp, // Current position within `text` [in-out param] | ||
| 127 | StringView prefix, | ||
| 128 | bool exact, | ||
| 129 | Tag *tag // [out param] | ||
| 130 | ) { | ||
| 131 |
2/2✓ Branch 14 → 3 taken 55 times.
✓ Branch 14 → 15 taken 4 times.
|
59 | for (size_t pos = *posp; pos < text.length; ) { |
| 132 | 55 | StringView line = buf_slice_next_line(text.data, &pos, text.length); | |
| 133 | 55 | if ( | |
| 134 |
1/2✓ Branch 4 → 5 taken 55 times.
✗ Branch 4 → 13 not taken.
|
55 | line.length > 0 // Line is non-empty |
| 135 |
2/2✓ Branch 5 → 6 taken 37 times.
✓ Branch 5 → 13 taken 18 times.
|
55 | && line.data[0] != '!' // and not a comment |
| 136 |
2/2✓ Branch 7 → 8 taken 15 times.
✓ Branch 7 → 13 taken 22 times.
|
37 | && strview_has_sv_prefix(line, prefix) // and starts with `prefix` |
| 137 |
4/4✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 13 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 13 taken 1 time.
|
15 | && (!exact || line.data[prefix.length] == '\t') // and matches `prefix` exactly, if applicable |
| 138 |
1/2✓ Branch 11 → 12 taken 14 times.
✗ Branch 11 → 13 not taken.
|
14 | && parse_ctags_line(tag, line) // and is a valid tags(5) entry |
| 139 | ) { | ||
| 140 | // Advance the position; `tag` has been filled by parse_ctags_line() | ||
| 141 | 14 | *posp = pos; | |
| 142 | 14 | return true; | |
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | // No matching tags remaining | ||
| 147 | 4 | return false; | |
| 148 | } | ||
| 149 | |||
| 150 | // NOTE: tag itself is not freed | ||
| 151 | 22 | void free_tag(Tag *tag) | |
| 152 | { | ||
| 153 | 22 | free(tag->pattern); | |
| 154 | 22 | } | |
| 155 |