File: | build-analysis/../src/clients/nycli/playlist_positions.c |
Warning: | line 131, column 26 Access to field 'data' results in a dereference of a null pointer (loaded from variable 'i') |
1 | /* XMMS2 - X Music Multiplexer System | |||
2 | * Copyright (C) 2003-2017 XMMS2 Team | |||
3 | * | |||
4 | * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!! | |||
5 | * | |||
6 | * This program is free software; you can redistribute it and/or | |||
7 | * modify it under the terms of the GNU General Public | |||
8 | * License as published by the Free Software Foundation; either | |||
9 | * version 2 of the License, or (at your option) any later version. | |||
10 | * | |||
11 | * This program is distributed in the hope that it will be useful, | |||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
14 | * General Public License for more details. | |||
15 | */ | |||
16 | ||||
17 | #include "playlist_positions.h" | |||
18 | ||||
19 | #include <stdlib.h> | |||
20 | #include <string.h> | |||
21 | ||||
22 | /** The goal is to parse the following grammar: | |||
23 | * | |||
24 | * positions := sequence | curr-context | curr-sequence | |||
25 | * sequence := interval | sequence "," interval | |||
26 | * curr-context := integer "_" integer | "_" integer | integer "_" | "_" | |||
27 | * curr-sequence := "+" sequence | "-" sequence | |||
28 | * interval := integer | integer "-" integer | "*" "-" integer | integer "-" "*" | |||
29 | * integer := digit | integer digit | |||
30 | * digit := [0-9] | |||
31 | */ | |||
32 | ||||
33 | /* Note: duplicates are excluded during the construction */ | |||
34 | struct playlist_positions_St { | |||
35 | gint cached_currpos; /* Needed for relative positions */ | |||
36 | GList *atoms; /* Int atoms, in decreasing order */ | |||
37 | GList *intervals; /* Intervals, in decreasing order */ | |||
38 | }; | |||
39 | ||||
40 | struct interval_St { | |||
41 | gint start; | |||
42 | gint end; | |||
43 | }; | |||
44 | ||||
45 | typedef struct interval_St interval_t; | |||
46 | ||||
47 | typedef enum { | |||
48 | INTERVAL_IS_GREATER, | |||
49 | ATOM_IS_GREATER | |||
50 | } interval_vs_atom_t; | |||
51 | ||||
52 | #define NEXT_POSITION(list, forward)forward ? ((list) ? (((GList *)(list))->prev) : ((void*)0) ) : ((list) ? (((GList *)(list))->next) : ((void*)0)) forward ? g_list_previous (list)((list) ? (((GList *)(list))->prev) : ((void*)0)) : g_list_next (list)((list) ? (((GList *)(list))->next) : ((void*)0)) | |||
53 | ||||
54 | static int playlist_positions_count (playlist_positions_t *positions); | |||
55 | static gboolean playlist_positions_parse_token (const gchar *expr, playlist_positions_t *p); | |||
56 | static gboolean playlist_positions_parse_sequence (const gchar *expr, playlist_positions_t *p); | |||
57 | static gboolean playlist_positions_parse_currcontext (const gchar *expr, playlist_positions_t *p); | |||
58 | static gboolean playlist_positions_parse_relsequence (const gchar *expr, playlist_positions_t *p); | |||
59 | static playlist_positions_t *playlist_positions_new (gint currpos); | |||
60 | static void playlist_positions_add_atom (playlist_positions_t *positions, gint x); | |||
61 | static gboolean playlist_positions_add_interval (playlist_positions_t *positions, gint start, gint end); | |||
62 | static gboolean playlist_positions_intervals_contains_atom (playlist_positions_t *positions, gint x); | |||
63 | ||||
64 | static interval_t *interval_new (gint start, gint end); | |||
65 | static void interval_free (interval_t *ival); | |||
66 | static interval_vs_atom_t interval_atom_list_cmp (GList *intervals, GList *atoms, gboolean forward); | |||
67 | static gboolean interval_parse (const gchar *expr, gint *start, gint *end); | |||
68 | static void interval_foreach (interval_t *ival, playlist_positions_func f, gboolean forward, void *userdata); | |||
69 | ||||
70 | ||||
71 | /* FIXME: not positions vs syntax error! */ | |||
72 | /* FIXME: allow forward and backward foreach */ | |||
73 | /* FIXME: implement for: add, move, jump, info, search/list? */ | |||
74 | ||||
75 | gboolean | |||
76 | playlist_positions_parse (const gchar *expr, playlist_positions_t **p, | |||
77 | gint currpos) | |||
78 | { | |||
79 | gchar **tokens; | |||
80 | playlist_positions_t *positions; | |||
81 | gint i = 0; | |||
82 | gboolean success = TRUE(!(0)); | |||
83 | ||||
84 | /* Empty/NULL string, or invalid chars, don't bother */ | |||
85 | if (!expr || !*expr || strspn (expr, "0123456789,_+- ") != strlen (expr)) { | |||
86 | return FALSE(0); | |||
87 | } | |||
88 | ||||
89 | tokens = g_strsplit (expr, " ", 0); | |||
90 | positions = playlist_positions_new (currpos); | |||
91 | ||||
92 | while (tokens[i]) { | |||
93 | if (!playlist_positions_parse_token (tokens[i], positions)) { | |||
94 | success = FALSE(0); | |||
95 | break; | |||
96 | } | |||
97 | i++; | |||
98 | } | |||
99 | ||||
100 | g_strfreev (tokens); | |||
101 | ||||
102 | if (success) { | |||
103 | *p = positions; | |||
104 | } else { | |||
105 | playlist_positions_free (positions); | |||
106 | } | |||
107 | ||||
108 | return success; | |||
109 | } | |||
110 | ||||
111 | void | |||
112 | playlist_positions_foreach (playlist_positions_t *positions, | |||
113 | playlist_positions_func f, gboolean forward, | |||
114 | void *userdata) | |||
115 | { | |||
116 | GList *i, *a; | |||
117 | interval_t *ival; | |||
118 | int atom; | |||
119 | ||||
120 | if (forward) { | |||
| ||||
121 | i = g_list_last (positions->intervals); | |||
122 | a = g_list_last (positions->atoms); | |||
123 | } else { | |||
124 | i = positions->intervals; | |||
125 | a = positions->atoms; | |||
126 | } | |||
127 | ||||
128 | while (i || a) { | |||
129 | switch (interval_atom_list_cmp (i, a, forward)) { | |||
130 | case INTERVAL_IS_GREATER: | |||
131 | ival = (interval_t *) i->data; | |||
| ||||
132 | interval_foreach (ival, f, forward, userdata); | |||
133 | i = NEXT_POSITION (i, forward)forward ? ((i) ? (((GList *)(i))->prev) : ((void*)0)) : (( i) ? (((GList *)(i))->next) : ((void*)0)); | |||
134 | break; | |||
135 | case ATOM_IS_GREATER: | |||
136 | atom = GPOINTER_TO_INT (a->data)((gint) (glong) (a->data)); | |||
137 | f (atom, userdata); | |||
138 | a = NEXT_POSITION (a, forward)forward ? ((a) ? (((GList *)(a))->prev) : ((void*)0)) : (( a) ? (((GList *)(a))->next) : ((void*)0)); | |||
139 | break; | |||
140 | } | |||
141 | } | |||
142 | } | |||
143 | ||||
144 | gboolean | |||
145 | playlist_positions_get_single (playlist_positions_t *positions, gint *pos) | |||
146 | { | |||
147 | gboolean retval; | |||
148 | ||||
149 | /* If a single (atom) position, get it */ | |||
150 | if (playlist_positions_count (positions) == 1 && positions->atoms) { | |||
151 | *pos = GPOINTER_TO_INT (positions->atoms->data)((gint) (glong) (positions->atoms->data)); | |||
152 | retval = TRUE(!(0)); | |||
153 | } else { | |||
154 | retval = FALSE(0); | |||
155 | } | |||
156 | ||||
157 | return retval; | |||
158 | } | |||
159 | ||||
160 | ||||
161 | /* Return the number of positions in the object. */ | |||
162 | static int | |||
163 | playlist_positions_count (playlist_positions_t *positions) | |||
164 | { | |||
165 | GList *n; | |||
166 | int count = g_list_length (positions->atoms); | |||
167 | ||||
168 | for (n = positions->intervals; n; n = g_list_next (n)((n) ? (((GList *)(n))->next) : ((void*)0))) { | |||
169 | interval_t *ival = (interval_t *) n->data; | |||
170 | count += ival->end - ival->start; | |||
171 | } | |||
172 | ||||
173 | return count; | |||
174 | } | |||
175 | ||||
176 | static playlist_positions_t * | |||
177 | playlist_positions_new (gint currpos) | |||
178 | { | |||
179 | playlist_positions_t *positions; | |||
180 | ||||
181 | positions = g_new0 (playlist_positions_t, 1)((playlist_positions_t *) g_malloc0_n ((1), sizeof (playlist_positions_t ))); | |||
182 | positions->cached_currpos = currpos; | |||
183 | ||||
184 | return positions; | |||
185 | } | |||
186 | ||||
187 | void | |||
188 | playlist_positions_free (playlist_positions_t *positions) | |||
189 | { | |||
190 | GList *n; | |||
191 | ||||
192 | g_list_free (positions->atoms); | |||
193 | ||||
194 | for (n = positions->intervals; n; n = g_list_next (n)((n) ? (((GList *)(n))->next) : ((void*)0))) { | |||
195 | interval_free ((interval_t *) n->data); | |||
196 | } | |||
197 | g_list_free (positions->intervals); | |||
198 | ||||
199 | g_free (positions); | |||
200 | } | |||
201 | ||||
202 | static gboolean | |||
203 | playlist_positions_parse_token (const gchar *expr, playlist_positions_t *p) | |||
204 | { | |||
205 | /* Empty string, don't bother */ | |||
206 | if (!*expr) { | |||
207 | return TRUE(!(0)); | |||
208 | } | |||
209 | ||||
210 | if (!playlist_positions_parse_sequence (expr, p) && | |||
211 | !playlist_positions_parse_currcontext (expr, p) && | |||
212 | !playlist_positions_parse_relsequence (expr, p)) { | |||
213 | return FALSE(0); | |||
214 | } | |||
215 | ||||
216 | return TRUE(!(0)); | |||
217 | } | |||
218 | ||||
219 | static gboolean | |||
220 | playlist_positions_parse_sequence (const gchar *expr, playlist_positions_t *p) | |||
221 | { | |||
222 | gchar **intervals; | |||
223 | gint i = 0; | |||
224 | gboolean success = TRUE(!(0)); | |||
225 | gint start, end; | |||
226 | ||||
227 | /* Quit on '+' or '-' prefix, which would be a relsequence */ | |||
228 | if (*expr == '+' || *expr == '-') { | |||
229 | return FALSE(0); | |||
230 | } | |||
231 | ||||
232 | intervals = g_strsplit (expr, ",", 0); | |||
233 | ||||
234 | while (intervals[i]) { | |||
235 | if (interval_parse (intervals[i], &start, &end)) { | |||
236 | if (start >= 0) { | |||
237 | /* note: user lists start at 1 */ | |||
238 | if (end >= 0) { | |||
239 | success = playlist_positions_add_interval (p, start - 1, end - 1); | |||
240 | } else { | |||
241 | playlist_positions_add_atom (p, start - 1); | |||
242 | } | |||
243 | } | |||
244 | } else { | |||
245 | success = FALSE(0); | |||
246 | break; | |||
247 | } | |||
248 | i++; | |||
249 | } | |||
250 | ||||
251 | g_strfreev (intervals); | |||
252 | ||||
253 | return success; | |||
254 | } | |||
255 | ||||
256 | static gboolean | |||
257 | playlist_positions_parse_relsequence (const gchar *expr, playlist_positions_t *p) | |||
258 | { | |||
259 | gchar **intervals; | |||
260 | gint i = 0; | |||
261 | gboolean success = TRUE(!(0)); | |||
262 | gint start, end, currpos; | |||
263 | ||||
264 | /* No '+' or '-' prefix, not a relsequence */ | |||
265 | if (*expr != '+' && *expr != '-') { | |||
266 | return FALSE(0); | |||
267 | } | |||
268 | ||||
269 | intervals = g_strsplit (expr + 1, ",", 0); | |||
270 | currpos = p->cached_currpos; | |||
271 | ||||
272 | if (currpos < 0) { | |||
273 | /* FIXME: error, cannot use relative currpos if not set */ | |||
274 | return FALSE(0); | |||
275 | } | |||
276 | ||||
277 | while (intervals[i] && success) { | |||
278 | if (interval_parse (intervals[i], &start, &end)) { | |||
279 | if (start >= 0) { | |||
280 | start = (*expr == '+') ? start : -start; | |||
281 | if (end >= 0) { | |||
282 | end = (*expr == '+') ? end : -end; | |||
283 | success = playlist_positions_add_interval (p, currpos + start, | |||
284 | currpos + end); | |||
285 | } else { | |||
286 | playlist_positions_add_atom (p, currpos + start); | |||
287 | } | |||
288 | } | |||
289 | } else { | |||
290 | success = FALSE(0); | |||
291 | break; | |||
292 | } | |||
293 | i++; | |||
294 | } | |||
295 | ||||
296 | g_strfreev (intervals); | |||
297 | ||||
298 | return success; | |||
299 | } | |||
300 | ||||
301 | static gboolean | |||
302 | playlist_positions_parse_currcontext (const gchar *expr, playlist_positions_t *p) | |||
303 | { | |||
304 | gint before, after; | |||
305 | gchar *underscore; | |||
306 | gchar *endptr; | |||
307 | gint currpos; | |||
308 | ||||
309 | /* parse [N]_[M] */ | |||
310 | ||||
311 | /* No '_' char, not a currcontext */ | |||
312 | underscore = strchr (expr, '_'); | |||
313 | if (!underscore) { | |||
314 | return FALSE(0); | |||
315 | } | |||
316 | ||||
317 | if (expr == underscore) { | |||
318 | /* No N value */ | |||
319 | before = 0; | |||
320 | } else { | |||
321 | before = strtol (expr, &endptr, 10); | |||
322 | if (endptr != underscore) { | |||
323 | /* Incorrect parsing, error! */ | |||
324 | return FALSE(0); | |||
325 | } | |||
326 | } | |||
327 | ||||
328 | if (*(underscore + 1) == '\0') { | |||
329 | /* No M value */ | |||
330 | after = 0; | |||
331 | } else { | |||
332 | after = strtol (underscore + 1, &endptr, 10); | |||
333 | if (*endptr != '\0') { | |||
334 | /* Incorrect parsing, error! */ | |||
335 | return FALSE(0); | |||
336 | } | |||
337 | } | |||
338 | ||||
339 | /* Parsed a valid interval! - note: user lists start at 1 */ | |||
340 | currpos = p->cached_currpos; | |||
341 | ||||
342 | if (currpos < 0) { | |||
343 | /* FIXME: error, cannot use relative currpos if not set */ | |||
344 | return FALSE(0); | |||
345 | } | |||
346 | ||||
347 | return playlist_positions_add_interval (p, currpos - before, | |||
348 | currpos + after); | |||
349 | } | |||
350 | ||||
351 | static void | |||
352 | playlist_positions_add_atom (playlist_positions_t *positions, gint x) | |||
353 | { | |||
354 | GList *n; | |||
355 | ||||
356 | /* Add atom if not already in an interval */ | |||
357 | if (!playlist_positions_intervals_contains_atom (positions, x)) { | |||
358 | for (n = positions->atoms; n; n = g_list_next (n)((n) ? (((GList *)(n))->next) : ((void*)0))) { | |||
359 | gint curr = GPOINTER_TO_INT (n->data)((gint) (glong) (n->data)); | |||
360 | if (curr == x) { | |||
361 | /* Already present, exit */ | |||
362 | return; | |||
363 | } else if (curr < x) { | |||
364 | break; | |||
365 | } | |||
366 | } | |||
367 | ||||
368 | /* Found the sorted position, insert the atom */ | |||
369 | positions->atoms = g_list_insert_before (positions->atoms, n, | |||
370 | GINT_TO_POINTER (x)((gpointer) (glong) (x))); | |||
371 | } | |||
372 | } | |||
373 | ||||
374 | static gboolean | |||
375 | playlist_positions_add_interval (playlist_positions_t *positions, | |||
376 | gint start, gint end) | |||
377 | { | |||
378 | GList *n; | |||
379 | interval_t *ival, *prev_ival; | |||
380 | ||||
381 | /* Invalid interval */ | |||
382 | if (start > end || start < 0) { | |||
383 | return FALSE(0); | |||
384 | } | |||
385 | ||||
386 | if (start == end) { | |||
387 | playlist_positions_add_atom (positions, start); | |||
388 | return TRUE(!(0)); | |||
389 | } | |||
390 | ||||
391 | /* Merge or add new interval in list */ | |||
392 | prev_ival = NULL((void*)0); | |||
393 | for (n = positions->intervals; n; n = g_list_next (n)((n) ? (((GList *)(n))->next) : ((void*)0))) { | |||
394 | ival = (interval_t *) n->data; | |||
395 | if (start > ival->end) { | |||
396 | break; | |||
397 | } else if (end < ival->start) { | |||
398 | continue; | |||
399 | } else { | |||
400 | /* Previous interval already merged, free it and extend current */ | |||
401 | if (prev_ival) { | |||
402 | ival->end = prev_ival->end; | |||
403 | interval_free (prev_ival); | |||
404 | positions->intervals = g_list_remove (positions->intervals, | |||
405 | prev_ival); | |||
406 | } | |||
407 | ||||
408 | prev_ival = ival; | |||
409 | } | |||
410 | } | |||
411 | ||||
412 | if (prev_ival) { | |||
413 | /* extend end of merged interval */ | |||
414 | if (end > prev_ival->end) { | |||
415 | prev_ival->end = end; | |||
416 | } | |||
417 | /* extend start of merged interval */ | |||
418 | if (start < prev_ival->start) { | |||
419 | prev_ival->start = start; | |||
420 | } | |||
421 | } else { | |||
422 | /* Not merged with an existing interval, create a new one */ | |||
423 | positions->intervals = g_list_insert_before (positions->intervals, n, | |||
424 | interval_new (start, end)); | |||
425 | } | |||
426 | ||||
427 | /* Remove atoms included in the new interval */ | |||
428 | for (n = positions->atoms; n; n = g_list_next (n)((n) ? (((GList *)(n))->next) : ((void*)0))) { | |||
429 | gint curr = GPOINTER_TO_INT (n->data)((gint) (glong) (n->data)); | |||
430 | if (curr <= end) { | |||
431 | if (curr >= start) { | |||
432 | /* atom in the interval, remove */ | |||
433 | positions->atoms = g_list_delete_link (positions->atoms, n); | |||
434 | } else { | |||
435 | /* atom smaller than interval, done */ | |||
436 | break; | |||
437 | } | |||
438 | } | |||
439 | } | |||
440 | ||||
441 | return TRUE(!(0)); | |||
442 | } | |||
443 | ||||
444 | static gboolean | |||
445 | playlist_positions_intervals_contains_atom (playlist_positions_t *positions, gint x) | |||
446 | { | |||
447 | GList *n; | |||
448 | ||||
449 | for (n = positions->intervals; n; n = g_list_next (n)((n) ? (((GList *)(n))->next) : ((void*)0))) { | |||
450 | interval_t *ival = (interval_t *) n->data; | |||
451 | if (x >= ival->start) { | |||
452 | if (x > ival->end) { | |||
453 | /* x larger than biggest interval */ | |||
454 | return FALSE(0); | |||
455 | } else { | |||
456 | /* x inside current interval */ | |||
457 | return TRUE(!(0)); | |||
458 | } | |||
459 | } | |||
460 | ||||
461 | /* Not in this interval, tried the next (smaller) */ | |||
462 | } | |||
463 | ||||
464 | return FALSE(0); | |||
465 | } | |||
466 | ||||
467 | static interval_t * | |||
468 | interval_new (gint start, gint end) | |||
469 | { | |||
470 | interval_t *ival; | |||
471 | ||||
472 | ival = g_new0 (interval_t, 1)((interval_t *) g_malloc0_n ((1), sizeof (interval_t))); | |||
473 | ival->start = start; | |||
474 | ival->end = end; | |||
475 | ||||
476 | return ival; | |||
477 | } | |||
478 | ||||
479 | static void | |||
480 | interval_free (interval_t *ival) | |||
481 | { | |||
482 | g_free (ival); | |||
483 | } | |||
484 | ||||
485 | static void | |||
486 | interval_foreach (interval_t *ival, playlist_positions_func f, | |||
487 | gboolean forward, void *userdata) | |||
488 | { | |||
489 | int i; | |||
490 | int start, end, inc; | |||
491 | ||||
492 | if (forward) { | |||
493 | start = ival->start; | |||
494 | end = ival->end + 1; | |||
495 | inc = 1; | |||
496 | } else { | |||
497 | start = ival->end; | |||
498 | end = ival->start - 1; | |||
499 | inc = -1; | |||
500 | } | |||
501 | ||||
502 | for (i = start; i != end; i+=inc) { | |||
503 | f (i, userdata); | |||
504 | } | |||
505 | } | |||
506 | ||||
507 | static gboolean | |||
508 | interval_parse (const gchar *expr, gint *start, gint *end) | |||
509 | { | |||
510 | gchar *endptr; | |||
511 | ||||
512 | *start = -1; | |||
513 | *end = -1; | |||
514 | ||||
515 | /* Empty string, don't bother */ | |||
516 | if (!*expr) { | |||
517 | return TRUE(!(0)); | |||
518 | } | |||
519 | ||||
520 | *start = strtol (expr, &endptr, 10); | |||
521 | ||||
522 | if (*endptr == '-') { | |||
523 | /* A real interval, get end */ | |||
524 | *end = strtol (endptr + 1, &endptr, 10); | |||
525 | if (*endptr != '\0') { | |||
526 | /* Incorrect parsing of end, error! */ | |||
527 | return FALSE(0); | |||
528 | } | |||
529 | } else if (*endptr != '\0') { | |||
530 | /* Incorrect parsing of start, error! */ | |||
531 | return FALSE(0); | |||
532 | } | |||
533 | ||||
534 | return TRUE(!(0)); | |||
535 | } | |||
536 | ||||
537 | static interval_vs_atom_t | |||
538 | interval_atom_list_cmp (GList *intervals, GList *atoms, gboolean forward) | |||
539 | { | |||
540 | interval_t *ival; | |||
541 | int atom; | |||
542 | ||||
543 | /* Empty lists lose */ | |||
544 | if (!intervals) { | |||
545 | return ATOM_IS_GREATER; | |||
546 | } | |||
547 | if (!atoms) { | |||
548 | return INTERVAL_IS_GREATER; | |||
549 | } | |||
550 | ||||
551 | /* Need to compare first items */ | |||
552 | ival = (interval_t *) intervals->data; | |||
553 | atom = GPOINTER_TO_INT (atoms->data)((gint) (glong) (atoms->data)); | |||
554 | ||||
555 | /* If forward invert comparison result */ | |||
556 | if (forward) { | |||
557 | return (atom < ival->end ? ATOM_IS_GREATER : INTERVAL_IS_GREATER); | |||
558 | } | |||
559 | ||||
560 | return (atom > ival->end ? ATOM_IS_GREATER : INTERVAL_IS_GREATER); | |||
561 | } |