Logo

index : track

---

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/track.git/html/src/parse.jai blob: 0c3ab5ea6a137980eb907a61be66f14a0d6f05b1 [raw] [clear marker]

        
0
1
2COMMENT_PREFIXES :: "(///?|/\\*{1,9}|#{1,9}|\\-\\-)";
3KEYWORDS :: "(TODO|NOTE|IDEA|CONTINUE)";
4AUTHOR :: "([@\\-_\\s\\d\\w]+)";
5PRIORITY :: "([-+]?\\d+)";
6
7RE_PATTERN :: #run -> string {
8 return tprint("%1\\s*%2:?(\\(%3[\\s]*,?[\\s]*%4?\\))?:?",
9 COMMENT_PREFIXES,
10 KEYWORDS,
11 AUTHOR,
12 PRIORITY
13 );
14}
15
16
17Token_Identifier :: enum {
18 NONE :: 0;
19 TODO;
20 NOTE;
21 IDEA;
22 CONTINUE;
23}
24
25Token :: struct {
26 identifier: Token_Identifier;
27 priority: int = 0;
28 author: string = "";
29 comment: string;
30 line: int;
31}
32
33Match :: struct {
34 match: string;
35 position: int;
36 length: int;
37 groups: []string;
38}
39
40
41init_pattern :: (mode: Re.ParseFlags = .FoldCase | .LikePerl) {
42 re = Re.compile(RE_PATTERN, mode);
43}
44
45matches_process :: (source: string, matches: [..]Match) -> [..]Token {
46
47 text_parse :: () -> string #expand {
48 terminator := terminator_get(`it.groups[0]);
49 text := consume_till(`source, terminator, `it.position + `it.length);
50 return text;
51 }
52
53 parse_int_try :: (s: *string) -> int {
54 v, success := parse_int(s);
55 if !success return 690000;
56 return v;
57 }
58
59
60 /** This is inefficient, but since the ordering from [..]Match
61 isn't always chronological, it would lead to a shitty program behavior.
62
63 Plus I don't expect that people have amalgamations in their projects.
64 */
65 lines_count :: (s: string, pos_till: int) -> int {
66 assert(pos_till < s.count-1, "Ooohhps");
67
68 lines: int = 1;
69 for i: 0..pos_till {
70 if s[i] == "\n" then lines += 1;
71 }
72
73 return lines;
74 }
75
76 count_non_empty_elements :: inline (groups: []string) -> count: int {
77 c: int;
78 for * groups {
79 if it.* then c += 1;
80 }
81 return c;
82 }
83
84
85 tokens: [..]Token;
86
87 for matches {
88 capture_count := count_non_empty_elements(it.groups);
89 if it.match.count == 0 then continue;
90 if capture_count == {
91
92 // Only KEYWORD
93 case 2;
94 text := text_parse();
95 token: Token;
96 token.identifier = identifier_determine(it.groups[1]);
97 token.comment = text;
98 token.line = lines_count(source, it.position);
99 array_add(*tokens, token);
100
101 // With author
102 case 4;
103 text := text_parse();
104 token: Token;
105 token.identifier = identifier_determine(it.groups[1]);
106 token.author = trim(it.groups[3]);
107 token.comment = text;
108 token.line = lines_count(source, it.position);
109 array_add(*tokens, token);
110
111 // With author and priority
112 case 5;
113 text := text_parse();
114 token: Token;
115 token.identifier = identifier_determine(it.groups[1]);
116 token.priority = parse_int_try(*trim(it.groups[4]));
117 token.author = trim(it.groups[3]);
118 token.comment = text;
119 token.line = lines_count(source, it.position);
120 array_add(*tokens, token);
121 }
122 }
123
124 return tokens;
125}
126
127matches_collect :: (source: string) -> (success: bool, matches: [..]Match) {
128 matches: [..]Match;
129 has_match := search_for(source, *matches);
130 return has_match, matches;
131}
132
133
134#scope_file
135
136
137re: Re.Regexp;
138
139
140is_at_end :: (n: int) -> bool #expand {
141 return n > `source.count-1;
142}
143
144skip_space_return_index :: inline (s: string, start_from: int) -> int {
145 for i: start_from..s.count-1 {
146 if s[i] != " "
147 && s[i] != "\t"
148 && s[i] != "\r"
149 && s[i] != 0x0B
150 then return i;
151 }
152 return start_from;
153}
154
155consume_till :: (source: string, terminator: string, pos: int) -> string {
156
157 buf: String_Builder;
158 init_string_builder(*buf);
159
160 // TODO(adam, 3): Quite a bad name, so the intent isn't really clear!
161 no_multiline_comment: bool = !(terminator == "*/");
162
163 consume_backend(
164 *buf,
165 pos,
166 source,
167 no_multiline_comment,
168 terminator
169 );
170
171 s := builder_to_string(*buf);
172 return trim(s);
173}
174
175consume_backend :: inline (
176 buf: *String_Builder,
177 pos: int,
178 source: string,
179 no_multiline_comment: bool,
180 terminator: string
181) {
182 /** Don't append tabs or whitespace, if it's an empty area like this:
183
184 # TODO: abc fooo baaar
185
186 */
187 more_than_one_space := false;
188 skip_next_iteration := false;
189
190 for i: pos..source.count-1 {
191 if skip_next_iteration {
192 skip_next_iteration = false;
193 continue;
194 }
195
196 if source[i] == {
197 case "\t"; #through;
198 case "\r"; #through;
199 case 0x0B; #through; // vertical tab
200 case " ";
201 if !more_than_one_space {
202 more_than_one_space = true;
203 append(buf, source[i]);
204 }
205 case "\n";
206 if no_multiline_comment ^ is_terminator(source, terminator, i+1) {
207 break;
208 }
209 if is_next_character(source, "\n", i+1) && no_multiline_comment {
210 break;
211 }
212 case "*";
213 if is_next_character(source, "/", i+1) {
214 break;
215 }
216 case;
217 more_than_one_space = false;
218
219 cond := true;
220 cond &= source[i] == terminator[0];
221
222 if terminator.count == 2 {
223 cond &= is_next_character(source, terminator[1], i+1);
224 if cond {
225 append(buf, " ");
226 skip_next_iteration = true;
227 }
228 }
229
230 if cond then continue;
231 append(buf, source[i]);
232 }
233 }
234}
235
236is_terminator :: (source: string, terminator: string, idx: int) -> bool {
237 offset := skip_space_return_index(source, idx);
238
239 cond := true;
240 cond &= !is_at_end(offset) && source[offset] == terminator[0];
241
242 if terminator.count == 2 {
243 cond &= !is_at_end(offset + 1) && source[offset + 1] == terminator[1];
244 }
245
246 return cond;
247}
248
249is_next_character :: inline (source: string, terminator: u8, idx: int) -> bool {
250 offset := skip_space_return_index(source, idx);
251 return !is_at_end(offset) && source[offset] == terminator;
252}
253
254terminator_get :: (starter: string) -> string {
255 if starts_with(starter, "/*") { // TS "fix" *//*
256 return "*/";
257 }
258 return starter;
259}
260
261identifier_determine :: (id: string) -> Token_Identifier {
262 if to_lower_copy(id) == {
263 case "todo"; return .TODO;
264 case "note"; return .NOTE;
265 case "idea"; return .IDEA;
266 case "continue"; return .CONTINUE;
267 case;
268 return .NONE;
269 }
270}
271
272search_for :: (
273 s: string,
274 matches: *[..]Match
275)
276 -> has_match: bool
277{
278 subject := s;
279 has_match := false;
280
281 while true {
282 if subject.count <= 0 break;
283
284 diff := s.count - subject.count;
285
286 matched, captures := Re.match(subject, re);
287
288 has_match |= matched;
289 if !matched break;
290
291 c := captures[0];
292 offset := c.data - subject.data;
293 count := c.count;
294
295 if offset < 0 || offset >= subject.count break;
296
297 advance := offset + count;
298 subject.data += advance;
299 subject.count -= advance;
300
301 match: Match;
302 match.match = c;
303 match.position = offset + diff;
304 match.length = c.count;
305
306 array_ordered_remove_by_index(*captures, 0);
307
308 match.groups = NewArray(captures.count, string);
309 for captures match.groups[it_index] = it;
310
311 array_add(matches, match);
312 }
313
314 return has_match;
315}
316
317
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit