Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <stdlib.h> | ||
2 | #include "ctags.h" | ||
3 | #include "util/ascii.h" | ||
4 | #include "util/debug.h" | ||
5 | #include "util/str-util.h" | ||
6 | #include "util/strtonum.h" | ||
7 | #include "util/xmalloc.h" | ||
8 | #include "util/xstring.h" | ||
9 | |||
10 | // Convert an ex(1) style pattern from a tags(5) file to a basic POSIX | ||
11 | // regex ("BRE"), so that it can be compiled with regcomp(3) | ||
12 | 19 | static size_t regex_from_ex_pattern ( | |
13 | const char *ex_str, | ||
14 | size_t len, | ||
15 | char **regex_str // out param | ||
16 | ) { | ||
17 | 19 | BUG_ON(len == 0); | |
18 | 19 | const char open_delim = ex_str[0]; | |
19 | 19 | BUG_ON(open_delim != '/' && open_delim != '?'); | |
20 | 19 | char *buf = xmalloc(xmul(2, len)); | |
21 | |||
22 | // The pattern isn't a real regex; special chars need to be escaped | ||
23 |
2/2✓ Branch 0 (20→9) taken 890 times.
✓ Branch 1 (20→21) taken 1 times.
|
891 | for (size_t i = 1, j = 0; i < len; i++) { |
24 | 890 | char c = ex_str[i]; | |
25 |
2/2✓ Branch 0 (9→10) taken 889 times.
✓ Branch 1 (9→21) taken 1 times.
|
890 | if (c == '\0') { |
26 | break; | ||
27 |
2/2✓ Branch 0 (10→11) taken 3 times.
✓ Branch 1 (10→14) taken 886 times.
|
889 | } else if (c == '\\') { |
28 |
1/2✓ Branch 0 (11→12) taken 3 times.
✗ Branch 1 (11→21) not taken.
|
3 | if (unlikely(++i >= len)) { |
29 | break; | ||
30 | } | ||
31 | 3 | c = ex_str[i]; | |
32 |
2/2✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→19) taken 2 times.
|
3 | if (c == '\\') { |
33 | // Escape "\\" as "\\" (any other "\x" becomes just "x") | ||
34 | 1 | buf[j++] = '\\'; | |
35 | } | ||
36 |
4/4✓ Branch 0 (14→15) taken 859 times.
✓ Branch 1 (14→16) taken 27 times.
✓ Branch 2 (15→16) taken 1 times.
✓ Branch 3 (15→17) taken 858 times.
|
886 | } else if (c == '*' || c == '[' || c == ']') { |
37 | 28 | buf[j++] = '\\'; | |
38 |
2/2✓ Branch 0 (17→18) taken 17 times.
✓ Branch 1 (17→19) taken 841 times.
|
858 | } else if (c == open_delim) { |
39 | 17 | buf[j] = '\0'; | |
40 | 17 | *regex_str = buf; | |
41 | 17 | return i + 1; | |
42 | } | ||
43 | 872 | buf[j++] = c; | |
44 | } | ||
45 | |||
46 | // End of string reached without a matching end delimiter; invalid input | ||
47 | 2 | free(buf); | |
48 | 2 | return 0; | |
49 | } | ||
50 | |||
51 | 21 | static size_t parse_ex_cmd(Tag *tag, const char *buf, size_t size) | |
52 | { | ||
53 |
1/2✓ Branch 0 (2→3) taken 21 times.
✗ Branch 1 (2→12) not taken.
|
21 | if (unlikely(size == 0)) { |
54 | return 0; | ||
55 | } | ||
56 | |||
57 | 21 | size_t n; | |
58 |
2/2✓ Branch 0 (3→4) taken 19 times.
✓ Branch 1 (3→5) taken 2 times.
|
21 | if (buf[0] == '/' || buf[0] == '?') { |
59 | 19 | n = regex_from_ex_pattern(buf, size, &tag->pattern); | |
60 | } else { | ||
61 | 2 | n = buf_parse_ulong(buf, size, &tag->lineno); | |
62 | } | ||
63 | |||
64 |
2/2✓ Branch 0 (6→7) taken 19 times.
✓ Branch 1 (6→12) taken 2 times.
|
21 | if (n == 0) { |
65 | return 0; | ||
66 | } | ||
67 | |||
68 |
4/4✓ Branch 0 (7→8) taken 17 times.
✓ Branch 1 (7→11) taken 2 times.
✓ Branch 2 (9→10) taken 16 times.
✓ Branch 3 (9→11) taken 1 times.
|
19 | bool trailing_comment = (n + 1 < size) && mem_equal(buf + n, ";\"", 2); |
69 | 19 | return n + (trailing_comment ? 2 : 0); | |
70 | } | ||
71 | |||
72 | 21 | bool parse_ctags_line(Tag *tag, const char *line, size_t line_len) | |
73 | { | ||
74 | 21 | size_t pos = 0; | |
75 | 21 | *tag = (Tag){.name = get_delim(line, &pos, line_len, '\t')}; | |
76 |
2/4✓ Branch 0 (3→4) taken 21 times.
✗ Branch 1 (3→25) not taken.
✓ Branch 2 (4→5) taken 21 times.
✗ Branch 3 (4→25) not taken.
|
21 | if (tag->name.length == 0 || pos >= line_len) { |
77 | return false; | ||
78 | } | ||
79 | |||
80 | 21 | tag->filename = get_delim(line, &pos, line_len, '\t'); | |
81 |
2/4✓ Branch 0 (6→7) taken 21 times.
✗ Branch 1 (6→25) not taken.
✓ Branch 2 (7→8) taken 21 times.
✗ Branch 3 (7→25) not taken.
|
21 | if (tag->filename.length == 0 || pos >= line_len) { |
82 | return false; | ||
83 | } | ||
84 | |||
85 | 21 | size_t len = parse_ex_cmd(tag, line + pos, line_len - pos); | |
86 |
2/2✓ Branch 0 (9→10) taken 2 times.
✓ Branch 1 (9→12) 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 0 (12→13) taken 18 times.
✓ Branch 1 (12→25) taken 1 times.
|
19 | if (pos >= line_len) { |
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 0 (13→14) taken 1 times.
✓ Branch 1 (13→24) taken 17 times.
|
18 | if (line[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 0 (24→16) taken 38 times.
✓ Branch 1 (24→25) taken 17 times.
|
55 | while (pos < line_len) { |
112 | 38 | StringView field = get_delim(line, &pos, line_len, '\t'); | |
113 |
3/4✓ Branch 0 (17→18) taken 17 times.
✓ Branch 1 (17→20) taken 21 times.
✓ Branch 2 (18→19) taken 17 times.
✗ Branch 3 (18→20) not taken.
|
38 | if (field.length == 1 && ascii_isalpha(field.data[0])) { |
114 | 17 | tag->kind = field.data[0]; | |
115 |
2/2✓ Branch 0 (21→22) taken 7 times.
✓ Branch 1 (21→23) 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 | const char *buf, | ||
126 | size_t buf_len, | ||
127 | size_t *posp, // in-out param | ||
128 | const StringView *prefix, | ||
129 | bool exact, | ||
130 | Tag *tag // out param | ||
131 | ) { | ||
132 | 18 | const char *p = prefix->data; | |
133 | 18 | size_t plen = prefix->length; | |
134 |
2/2✓ Branch 0 (14→3) taken 55 times.
✓ Branch 1 (14→15) taken 4 times.
|
59 | for (size_t pos = *posp; pos < buf_len; ) { |
135 | 55 | StringView line = buf_slice_next_line(buf, &pos, buf_len); | |
136 | 55 | if ( | |
137 |
1/2✓ Branch 0 (4→5) taken 55 times.
✗ Branch 1 (4→13) not taken.
|
55 | line.length > 0 // Line is non-empty |
138 |
2/2✓ Branch 0 (5→6) taken 37 times.
✓ Branch 1 (5→13) taken 18 times.
|
55 | && line.data[0] != '!' // and not a comment |
139 |
2/2✓ Branch 0 (7→8) taken 15 times.
✓ Branch 1 (7→13) taken 22 times.
|
37 | && strview_has_strn_prefix(&line, p, plen) // and starts with `prefix` |
140 |
4/4✓ Branch 0 (8→9) taken 2 times.
✓ Branch 1 (8→10) taken 13 times.
✓ Branch 2 (9→10) taken 1 times.
✓ Branch 3 (9→13) taken 1 times.
|
15 | && (!exact || line.data[plen] == '\t') // and matches `prefix` exactly, if applicable |
141 |
1/2✓ Branch 0 (11→12) taken 14 times.
✗ Branch 1 (11→13) not taken.
|
14 | && parse_ctags_line(tag, line.data, line.length) // and is a valid tags(5) entry |
142 | ) { | ||
143 | // Advance the position; `tag` has been filled by parse_ctags_line() | ||
144 | 14 | *posp = pos; | |
145 | 14 | return true; | |
146 | } | ||
147 | } | ||
148 | |||
149 | // No matching tags remaining | ||
150 | 4 | return false; | |
151 | } | ||
152 | |||
153 | // NOTE: tag itself is not freed | ||
154 | 22 | void free_tag(Tag *tag) | |
155 | { | ||
156 | 22 | free(tag->pattern); | |
157 | 22 | } | |
158 |