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