A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "goban.h"
23#include "sgf_parse.h"
24#include "sgf.h"
25#include "sgf_storage.h"
26#include "util.h"
27#include "board.h"
28#include "game.h"
29
30static void handle_prop_value (enum prop_type_t type);
31static int read_prop_value (char *buffer, size_t buffer_size);
32static void do_range (enum prop_type_t type, unsigned short ul,
33 unsigned short br);
34static void parse_prop (void);
35static void parse_node (void);
36static enum prop_type_t parse_prop_type (void);
37
38static unsigned short sgf_to_pos (char *buffer);
39
40bool
41parse_sgf (const char *filename)
42{
43 int saved = current_node;
44
45 /* for parsing */
46 int first_handle = 0; /* first node in the branch */
47 int file_position = 0;
48
49 int temp;
50
51 close_file (&sgf_fd);
52
53 sgf_fd = rb->open (filename, O_RDONLY);
54
55 if (sgf_fd < 0)
56 {
57 return false;
58 }
59
60 current_node = start_node;
61
62 if (current_node < 0)
63 {
64 current_node = saved;
65 return false;
66 }
67
68 empty_stack (&parse_stack);
69
70 /* seek to the first '(' */
71 while (peek_char_no_whitespace (sgf_fd) != '(')
72 {
73 if (read_char_no_whitespace (sgf_fd) == -1)
74 {
75 DEBUGF ("end of file or error before we found a '('\n");
76 current_node = saved;
77 return false;
78 }
79 }
80
81 push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
82 push_int_stack (&parse_stack, current_node);
83
84 while (pop_int_stack (&parse_stack, &first_handle) &&
85 pop_int_stack (&parse_stack, &file_position))
86 {
87 /* DEBUGF("poped off %d\n", file_position); */
88
89 rb->yield ();
90
91 current_node = first_handle;
92
93 if (file_position == -1)
94 {
95 temp = read_char_no_whitespace (sgf_fd);
96 if (temp != '(')
97 {
98 /* we're here because there may have been a sibling after
99 another gametree that was handled, but there's no '(',
100 so there wasnt' a sibling, so just go on to any more
101 gametrees in the stack */
102 continue;
103 }
104 else
105 {
106 /* there may be more siblings after we process this one */
107 push_int_stack (&parse_stack, -1);
108 push_int_stack (&parse_stack, first_handle);
109 }
110 }
111 else
112 {
113 /* check for a sibling after we finish with this node */
114 push_int_stack (&parse_stack, -1);
115 push_int_stack (&parse_stack, first_handle);
116
117 rb->lseek (sgf_fd, file_position, SEEK_SET);
118
119
120 /* we're at the start of a gametree here, right at the '(' */
121 temp = read_char_no_whitespace (sgf_fd);
122
123 if (temp != '(')
124 {
125 DEBUGF ("start of gametree doesn't have a '('!\n");
126 current_node = saved;
127 return false;
128 }
129 }
130
131 while (1)
132 {
133 temp = peek_char_no_whitespace (sgf_fd);
134 /* DEBUGF("||| %d, %c\n", absolute_position(), (char) temp); */
135
136 if (temp == ';')
137 {
138 /* fill the tree_head node before moving on */
139 if (current_node != tree_head ||
140 get_node (current_node)->props >= 0)
141 {
142 int temp = add_child_sgf (NULL);
143
144 if (temp >= 0)
145 {
146 current_node = temp;
147 }
148 else
149 {
150 rb->splash (2 * HZ, "Out of memory while parsing!");
151 return false;
152 }
153 }
154
155
156 read_char_no_whitespace (sgf_fd);
157 parse_node ();
158 }
159 else if (temp == ')')
160 {
161 /* finished this gametree */
162
163 /* we want to end one past the ')', so eat it up: */
164 read_char_no_whitespace (sgf_fd);
165 break;
166 }
167 else if (temp == '(')
168 {
169 /*
170 DEBUGF ("adding %d\n", (int) rb->lseek (sgf_fd, 0,
171 SEEK_CUR)); */
172 push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
173 push_int_stack (&parse_stack, current_node);
174
175 break;
176 }
177 else if (temp == -1)
178 {
179 break;
180 }
181 else
182 {
183 DEBUGF ("extra characters found while parsing: %c\n", temp);
184 /* skip the extras i guess */
185 read_char_no_whitespace (sgf_fd);
186 }
187 }
188 }
189
190 current_node = get_node (tree_head)->next;
191 while (current_node >= 0 && get_node (current_node)->props < 0)
192 {
193 temp = current_node; /* to be freed later */
194
195 /* update the ->prev pointed on all branches of the next node */
196 current_node = get_node (current_node)->next;
197 /* DEBUGF("trying to set prev for branch %d\n", current_node); */
198 if (current_node >= 0)
199 {
200 get_node (current_node)->prev = tree_head;
201
202 struct prop_t *loop_prop =
203 get_prop (get_node (current_node)->props);
204
205 while (loop_prop != 0)
206 {
207 if (loop_prop->type == PROP_VARIATION)
208 {
209 get_node (loop_prop->data.number)->prev = tree_head;
210 }
211 else
212 {
213 /* all of the variations have to be up front, so we
214 can quit here */
215 break;
216 }
217 loop_prop = get_prop (loop_prop->next);
218 }
219 }
220
221 /* update the tree head */
222 get_node (tree_head)->next = get_node (temp)->next;
223 /* DEBUGF("freeing %d %d %d\n", temp, start_node, saved); */
224 if (start_node == temp || saved == temp)
225 {
226 start_node = saved = tree_head;
227 }
228 free_storage_sgf (temp);
229
230 current_node = get_node (tree_head)->next;
231 }
232
233 current_node = saved;
234
235
236 /* DEBUGF("got past!\n"); */
237 close_file (&sgf_fd);
238 return true;
239}
240
241
242static void
243parse_node (void)
244{
245 int temp;
246
247 while (1)
248 {
249 temp = peek_char_no_whitespace (sgf_fd);
250
251 if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
252 {
253 return;
254 }
255 else
256 {
257 parse_prop ();
258 }
259 }
260}
261
262
263
264int start_of_prop = 0;
265static void
266parse_prop (void)
267{
268 enum prop_type_t temp_type = PROP_INVALID;
269 int temp;
270
271
272 while (1)
273 {
274 temp = peek_char_no_whitespace (sgf_fd);
275
276 if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
277 {
278 return;
279 }
280 else if (temp == '[')
281 {
282 handle_prop_value (temp_type);
283 }
284 else
285 {
286 start_of_prop = rb->lseek (sgf_fd, 0, SEEK_CUR);
287 temp_type = parse_prop_type ();
288 }
289 }
290}
291
292static enum prop_type_t
293parse_prop_type (void)
294{
295 char buffer[3];
296 int pos = 0;
297 int temp;
298
299 rb->memset (buffer, 0, sizeof (buffer));
300
301 while (1)
302 {
303 temp = peek_char_no_whitespace (sgf_fd);
304
305 if (temp == ';' || temp == '[' || temp == '(' ||
306 temp == -1 || temp == ')')
307 {
308 if (pos == 1 || pos == 2)
309 {
310 break;
311 }
312 else
313 {
314 return PROP_INVALID;
315 }
316 }
317 else if (temp >= 'A' && temp <= 'Z')
318 {
319 buffer[pos++] = temp;
320
321 if (pos == 2)
322 {
323 read_char_no_whitespace (sgf_fd);
324 break;
325 }
326 }
327
328 temp = read_char_no_whitespace (sgf_fd);
329 }
330
331 /* check if we're still reading a prop name, in which case we fail
332 (but first we want to eat up the rest of the prop name) */
333 bool failed = false;
334 while (peek_char_no_whitespace (sgf_fd) != ';' &&
335 peek_char_no_whitespace (sgf_fd) != '[' &&
336 peek_char_no_whitespace (sgf_fd) != '(' &&
337 peek_char_no_whitespace (sgf_fd) != '}' &&
338 peek_char_no_whitespace (sgf_fd) != -1)
339 {
340 failed = true;
341 read_char_no_whitespace (sgf_fd);
342 }
343
344 if (failed)
345 {
346 return PROP_INVALID;
347 }
348
349 int i;
350 for (i = 0; i < PROP_NAMES_SIZE; ++i)
351 {
352 if (rb->strcmp (buffer, prop_names[i]) == 0)
353 {
354 return (enum prop_type_t) i;
355 }
356 }
357 return PROP_INVALID;
358}
359
360static int
361read_prop_value (char *buffer, size_t buffer_size)
362{
363 bool escaped = false;
364 int temp;
365 int bytes_read = 0;
366
367 /* make it a string, the lazy way */
368 rb->memset (buffer, 0, buffer_size);
369 --buffer_size;
370
371 if (peek_char (sgf_fd) == '[')
372 {
373 read_char (sgf_fd);
374 }
375
376 while (1)
377 {
378 temp = read_char (sgf_fd);
379 if (temp == ']' && !escaped)
380 {
381 return bytes_read;
382 }
383 else if (temp == '\\')
384 {
385 if (escaped)
386 {
387 if (buffer && buffer_size)
388 {
389 *(buffer++) = temp;
390 ++bytes_read;
391 --buffer_size;
392 }
393 }
394 escaped = !escaped;
395 }
396 else if (temp == -1)
397 {
398 return bytes_read;
399 }
400 else
401 {
402 escaped = false;
403 if (buffer && buffer_size)
404 {
405 *(buffer++) = temp;
406 ++bytes_read;
407 --buffer_size;
408 }
409 }
410 }
411}
412
413static void
414handle_prop_value (enum prop_type_t type)
415{
416 /* max size of generically supported prop values is 6, which is 5 for
417 a point range ab:cd and one for the \0
418
419 (this buffer is only used for them, things such as white and black
420 player names are stored in different buffers) */
421
422 /* make it a little bigger for other random crap, like reading in time
423 */
424#define PROP_HANDLER_BUFFER_SIZE 16
425
426 char real_buffer[PROP_HANDLER_BUFFER_SIZE];
427 char *buffer = real_buffer;
428
429 int temp;
430 union prop_data_t temp_data;
431 bool in_prop_value = false;
432 bool escaped = false;
433 bool done = false;
434 int temp_width, temp_height;
435 unsigned short temp_pos_ul, temp_pos_br;
436 int temp_size;
437 char *temp_buffer;
438 bool got_value;
439
440 /* special extra handling for root properties, set a marker telling us
441 the right place to spit the values out in output_sgf */
442 if (type == PROP_GAME ||
443 type == PROP_APPLICATION ||
444 type == PROP_CHARSET ||
445 type == PROP_SIZE ||
446 type == PROP_FILE_FORMAT || type == PROP_VARIATION_TYPE)
447 {
448 header_marked = true;
449
450 temp_data.number = 0; /* meaningless */
451
452 /* don't add more than one, so just set it if we found one already
453 */
454 add_or_set_prop_sgf (current_node, PROP_ROOT_PROPS, temp_data);
455 }
456
457
458 if (!is_handled_sgf (type) || type == PROP_COMMENT)
459 {
460 /* DEBUGF("unhandled prop %d\n", (int) type); */
461 rb->lseek (sgf_fd, start_of_prop, SEEK_SET);
462
463 temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR);
464 /* absolute_position(&unhandled_prop_list); */
465
466 add_prop_sgf (current_node,
467 type == PROP_COMMENT ? PROP_COMMENT :
468 PROP_GENERIC_UNHANDLED, temp_data);
469
470 got_value = false;
471 while (!done)
472 {
473 temp = peek_char (sgf_fd);
474
475 switch (temp)
476 {
477 case -1:
478 done = true;
479 break;
480
481 case '\\':
482 if (got_value && !in_prop_value)
483 {
484 done = true;
485 }
486 escaped = !escaped;
487 break;
488 case '[':
489 escaped = false;
490 in_prop_value = true;
491 got_value = true;
492 break;
493 case ']':
494 if (!escaped)
495 {
496 in_prop_value = false;
497 }
498 escaped = false;
499 break;
500 case ')':
501 case '(':
502 case ';':
503 if (!in_prop_value)
504 {
505 done = true;
506 }
507 escaped = false;
508 break;
509 default:
510 if (got_value && !in_prop_value)
511 {
512 if (!is_whitespace (temp))
513 {
514 done = true;
515 }
516 }
517 escaped = false;
518 break;
519 };
520
521 if (done)
522 {
523 write_char (unhandled_fd, ';');
524 }
525 else
526 {
527 /* don't write out-of-prop whitespace */
528 if (in_prop_value || !is_whitespace (temp))
529 {
530 write_char (unhandled_fd, (char) temp);
531 }
532
533 read_char (sgf_fd);
534 }
535 }
536
537
538 return;
539 }
540 else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE)
541 {
542 /* DEBUGF("move prop %d\n", (int) type); */
543
544 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
545
546 temp_data.position = INVALID_POS;
547
548 /* empty is apparently acceptable as a pass */
549 if (temp == 0)
550 {
551 temp_data.position = PASS_POS;
552 }
553 else if (temp == 2)
554 {
555 temp_data.position = sgf_to_pos (buffer);
556 }
557 else
558 {
559 DEBUGF ("invalid move position read in, of wrong size!\n");
560 }
561
562
563 if (temp_data.position != INVALID_POS)
564 {
565 add_prop_sgf (current_node, type, temp_data);
566 }
567
568 return;
569 }
570 else if (type == PROP_ADD_BLACK ||
571 type == PROP_ADD_WHITE ||
572 type == PROP_ADD_EMPTY ||
573 type == PROP_CIRCLE ||
574 type == PROP_SQUARE ||
575 type == PROP_TRIANGLE ||
576 type == PROP_DIM || type == PROP_MARK || type == PROP_SELECTED)
577 {
578 /* DEBUGF("add prop %d\n", (int) type); */
579
580 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
581 if (temp == 2)
582 {
583 temp_data.position = sgf_to_pos (buffer);
584
585 if (temp_data.position != INVALID_POS &&
586 temp_data.position != PASS_POS)
587 {
588 add_prop_sgf (current_node, type, temp_data);
589 }
590 }
591 else if (temp == 5)
592 {
593 /* example: "ab:cd", two positions separated by a colon */
594 temp_pos_ul = sgf_to_pos (buffer);
595 temp_pos_br = sgf_to_pos (&(buffer[3]));
596
597 if (!on_board (temp_pos_ul) || !on_board (temp_pos_br) ||
598 buffer[2] != ':')
599 {
600 DEBUGF ("invalid range value!\n");
601 }
602
603 do_range (type, temp_pos_ul, temp_pos_br);
604 }
605 else
606 {
607 DEBUGF ("invalid position or range read in. wrong size!\n");
608 }
609 return;
610 }
611 else if (type == PROP_LABEL)
612 {
613 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
614
615 if (temp < 4 || buffer[2] != ':')
616 {
617 DEBUGF ("invalid LaBel property '%s'", buffer);
618 }
619 temp_data.position = sgf_to_pos (buffer);
620
621 if (!on_board (temp_data.position))
622 {
623 DEBUGF ("LaBel set on invalid position!\n");
624 }
625
626 temp_data.label_extra = buffer[3];
627
628 add_prop_sgf (current_node, type, temp_data);
629 return;
630 }
631 else if (type == PROP_GAME)
632 {
633 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
634 if (temp != 1 || buffer[0] != '1')
635 {
636 rb->splash (2 * HZ, "This isn't a Go SGF!! Parsing stopped.");
637 DEBUGF ("incorrect game type loaded!\n");
638
639 close_file (&sgf_fd);
640 }
641 }
642 else if (type == PROP_FILE_FORMAT)
643 {
644 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
645 if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4'))
646 {
647 rb->splash (2 * HZ, "Wrong SGF file version! Parsing stopped.");
648 DEBUGF ("can't handle file format %c\n", buffer[0]);
649
650 close_file (&sgf_fd);
651 }
652 }
653 else if (type == PROP_APPLICATION ||
654 type == PROP_CHARSET || type == PROP_VARIATION_TYPE)
655 {
656 /* we don't care. on output we'll write our own values for these */
657 read_prop_value (NULL, 0);
658 }
659 else if (type == PROP_SIZE)
660 {
661 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
662 if (temp == 0)
663 {
664 rb->splash (HZ, "Invalid board size specified in file.");
665 }
666 else
667 {
668 temp_width = rb->atoi (buffer);
669 while (*buffer != ':' && *buffer != '\0')
670 {
671 ++buffer;
672 }
673
674 if (*buffer != '\0')
675 {
676 ++buffer;
677 temp_height = rb->atoi (buffer);
678 }
679 else
680 {
681 temp_height = temp_width;
682 }
683
684
685 if (!set_size_board (temp_width, temp_height))
686 {
687 rb->splashf (HZ,
688 "Board too big/small! (%dx%d) Stopping parse.",
689 temp_width, temp_height);
690 close_file (&sgf_fd);
691 }
692 else
693 {
694 clear_board ();
695 }
696 }
697 }
698 else if (type == PROP_KOMI)
699 {
700 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
701
702 if (temp == 0)
703 {
704 header.komi = 0;
705 DEBUGF ("invalid komi specification. setting to zero\n");
706 }
707 else
708 {
709 header.komi = rb->atoi (buffer) << 1;
710 while (*buffer != '.' && *buffer != ',' && *buffer != '\0')
711 {
712 ++buffer;
713 }
714
715 if (*buffer != '\0')
716 {
717 ++buffer;
718
719 if (*buffer == 0)
720 {
721 /* do nothing */
722 }
723 else if (*buffer >= '1' && *buffer <= '9')
724 {
725 header.komi += 1;
726 }
727 else
728 {
729 if (*buffer != '0')
730 {
731 DEBUGF ("extra characters after komi value!\n");
732 }
733 }
734 }
735 }
736 }
737 else if (type == PROP_BLACK_NAME ||
738 type == PROP_WHITE_NAME ||
739 type == PROP_BLACK_RANK ||
740 type == PROP_WHITE_RANK ||
741 type == PROP_BLACK_TEAM ||
742 type == PROP_WHITE_TEAM ||
743 type == PROP_DATE ||
744 type == PROP_ROUND ||
745 type == PROP_EVENT ||
746 type == PROP_PLACE ||
747 type == PROP_OVERTIME ||
748 type == PROP_RESULT || type == PROP_RULESET)
749 {
750 if (!get_header_string_and_size
751 (&header, type, &temp_buffer, &temp_size))
752 {
753 rb->splash (5 * HZ,
754 "Error getting header string. Report this.");
755 }
756 else
757 {
758 temp = read_prop_value (temp_buffer, temp_size - 1);
759#if 0
760 DEBUGF ("read %d bytes into header for type: %d\n", temp, type);
761 DEBUGF ("data: %s\n", temp_buffer);
762#endif
763 }
764 }
765 else if (type == PROP_TIME_LIMIT)
766 {
767 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
768 header.time_limit = rb->atoi (buffer);
769 DEBUGF ("setting time: %d (%s)\n", header.time_limit, buffer);
770 }
771 else if (type == PROP_HANDICAP)
772 {
773 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
774 if (start_node == tree_head)
775 {
776 if (rb->atoi (buffer) >= 2)
777 {
778 start_node = current_node;
779 temp_data.number = header.handicap = rb->atoi (buffer);
780 add_prop_sgf (current_node, type, temp_data);
781 DEBUGF ("setting handicap: %d\n", header.handicap);
782 }
783 else
784 {
785 DEBUGF ("invalid HAndicap prop. ignoring\n");
786 }
787 }
788 else
789 {
790 rb->splash (HZ, "extraneous HAndicap prop present in file!\n");
791 }
792 }
793 else
794 {
795 DEBUGF ("UNHANDLED PROP TYPE!!!\n");
796 rb->splash (3 * HZ,
797 "A SGF prop was not dealt with. Please report this");
798 read_prop_value (NULL, 0);
799 }
800}
801
802
803
804/* upper-left and bottom right */
805static void
806do_range (enum prop_type_t type, unsigned short ul, unsigned short br)
807{
808 /* this code is overly general and accepts ranges even if ul and br
809 aren't the required corners it's easier doing that that failing if
810 the input is bad */
811
812 bool x_reverse = false;
813 bool y_reverse = false;
814 union prop_data_t temp_data;
815
816 if (I (br) < I (ul))
817 {
818 x_reverse = true;
819 }
820
821 if (J (br) < J (ul))
822 {
823 y_reverse = true;
824 }
825
826 int x, y;
827 for (x = I (ul);
828 x_reverse ? (x >= I (br)) : (x <= I (br)); x_reverse ? --x : ++x)
829 {
830 for (y = J (ul);
831 y_reverse ? (y >= J (br)) : (y <= J (br)); y_reverse ? --y : ++y)
832 {
833 temp_data.position = POS (x, y);
834
835 DEBUGF ("adding %d %d for range (type %d)\n",
836 I (temp_data.position), J (temp_data.position), type);
837 add_prop_sgf (current_node, type, temp_data);
838 }
839 }
840}
841
842
843
844static unsigned short
845sgf_to_pos (char *buffer)
846{
847 if (buffer[0] == 't' && buffer[1] == 't')
848 {
849 return PASS_POS;
850 }
851 else if (buffer[0] < 'a' || buffer[0] > 'z' ||
852 buffer[1] < 'a' || buffer[1] > 'z')
853 {
854 return INVALID_POS;
855 }
856 return POS (buffer[0] - 'a', buffer[1] - 'a');
857}
858