summaryrefslogtreecommitdiff
path: root/apps/plugins/goban/sgf_parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/goban/sgf_parse.c')
-rw-r--r--apps/plugins/goban/sgf_parse.c857
1 files changed, 857 insertions, 0 deletions
diff --git a/apps/plugins/goban/sgf_parse.c b/apps/plugins/goban/sgf_parse.c
new file mode 100644
index 0000000000..e0fa8fd2df
--- /dev/null
+++ b/apps/plugins/goban/sgf_parse.c
@@ -0,0 +1,857 @@
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}
291static enum prop_type_t
292parse_prop_type (void)
293{
294 char buffer[3];
295 int pos = 0;
296 int temp;
297
298 rb->memset (buffer, 0, sizeof (buffer));
299
300 while (1)
301 {
302 temp = peek_char_no_whitespace (sgf_fd);
303
304 if (temp == ';' || temp == '[' || temp == '(' ||
305 temp == -1 || temp == ')')
306 {
307 if (pos == 1 || pos == 2)
308 {
309 break;
310 }
311 else
312 {
313 return PROP_INVALID;
314 }
315 }
316 else if (temp >= 'A' && temp <= 'Z')
317 {
318 buffer[pos++] = temp;
319
320 if (pos == 2)
321 {
322 read_char_no_whitespace (sgf_fd);
323 break;
324 }
325 }
326
327 temp = read_char_no_whitespace (sgf_fd);
328 }
329
330 /* check if we're still reading a prop name, in which case we fail
331 (but first we want to eat up the rest of the prop name) */
332 bool failed = false;
333 while (peek_char_no_whitespace (sgf_fd) != ';' &&
334 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) != -1)
338 {
339 failed = true;
340 read_char_no_whitespace (sgf_fd);
341 }
342
343 if (failed)
344 {
345 return PROP_INVALID;
346 }
347
348 int i;
349 for (i = 0; i < PROP_NAMES_SIZE; ++i)
350 {
351 if (rb->strcmp (buffer, prop_names[i]) == 0)
352 {
353 return (enum prop_type_t) i;
354 }
355 }
356 return PROP_INVALID;
357}
358
359static int
360read_prop_value (char *buffer, size_t buffer_size)
361{
362 bool escaped = false;
363 int temp;
364 int bytes_read = 0;
365
366 /* make it a string, the lazy way */
367 rb->memset (buffer, 0, buffer_size);
368 --buffer_size;
369
370 if (peek_char (sgf_fd) == '[')
371 {
372 read_char (sgf_fd);
373 }
374
375 while (1)
376 {
377 temp = read_char (sgf_fd);
378 if (temp == ']' && !escaped)
379 {
380 return bytes_read;
381 }
382 else if (temp == '\\')
383 {
384 if (escaped)
385 {
386 if (buffer && buffer_size)
387 {
388 *(buffer++) = temp;
389 ++bytes_read;
390 --buffer_size;
391 }
392 }
393 escaped = !escaped;
394 }
395 else if (temp == -1)
396 {
397 return bytes_read;
398 }
399 else
400 {
401 escaped = false;
402 if (buffer && buffer_size)
403 {
404 *(buffer++) = temp;
405 ++bytes_read;
406 --buffer_size;
407 }
408 }
409 }
410}
411
412static void
413handle_prop_value (enum prop_type_t type)
414{
415 /* max size of generically supported prop values is 6, which is 5 for
416 a point range ab:cd and one for the \0
417
418 (this buffer is only used for them, things such as white and black
419 player names are stored in different buffers) */
420
421 /* make it a little bigger for other random crap, like reading in time
422 */
423#define PROP_HANDLER_BUFFER_SIZE 16
424
425 char real_buffer[PROP_HANDLER_BUFFER_SIZE];
426 char *buffer = real_buffer;
427
428 int temp;
429 union prop_data_t temp_data;
430 bool in_prop_value = false;
431 bool escaped = false;
432 bool done = false;
433 int temp_width, temp_height;
434 unsigned short temp_pos_ul, temp_pos_br;
435 int temp_size;
436 char *temp_buffer;
437 bool got_value;
438
439 /* special extra handling for root properties, set a marker telling us
440 the right place to spit the values out in output_sgf */
441 if (type == PROP_GAME ||
442 type == PROP_APPLICATION ||
443 type == PROP_CHARSET ||
444 type == PROP_SIZE ||
445 type == PROP_FILE_FORMAT || type == PROP_VARIATION_TYPE)
446 {
447 header_marked = true;
448
449 temp_data.number = 0; /* meaningless */
450
451 /* don't add more than one, so just set it if we found one already
452 */
453 add_or_set_prop_sgf (current_node, PROP_ROOT_PROPS, temp_data);
454 }
455
456
457 if (!is_handled_sgf (type) || type == PROP_COMMENT)
458 {
459 /* DEBUGF("unhandled prop %d\n", (int) type); */
460 rb->lseek (sgf_fd, start_of_prop, SEEK_SET);
461
462 temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR);
463 /* absolute_position(&unhandled_prop_list); */
464
465 add_prop_sgf (current_node,
466 type == PROP_COMMENT ? PROP_COMMENT :
467 PROP_GENERIC_UNHANDLED, temp_data);
468
469 got_value = false;
470 while (!done)
471 {
472 temp = peek_char (sgf_fd);
473
474 switch (temp)
475 {
476 case -1:
477 done = true;
478 break;
479
480 case '\\':
481 if (got_value && !in_prop_value)
482 {
483 done = true;
484 }
485 escaped = !escaped;
486 break;
487 case '[':
488 escaped = false;
489 in_prop_value = true;
490 got_value = true;
491 break;
492 case ']':
493 if (!escaped)
494 {
495 in_prop_value = false;
496 }
497 escaped = false;
498 break;
499 case ')':
500 case '(':
501 case ';':
502 if (!in_prop_value)
503 {
504 done = true;
505 }
506 escaped = false;
507 break;
508 default:
509 if (got_value && !in_prop_value)
510 {
511 if (!is_whitespace (temp))
512 {
513 done = true;
514 }
515 }
516 escaped = false;
517 break;
518 };
519
520 if (done)
521 {
522 write_char (unhandled_fd, ';');
523 }
524 else
525 {
526 /* don't write out-of-prop whitespace */
527 if (in_prop_value || !is_whitespace (temp))
528 {
529 write_char (unhandled_fd, (char) temp);
530 }
531
532 read_char (sgf_fd);
533 }
534 }
535
536
537 return;
538 }
539 else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE)
540 {
541 /* DEBUGF("move prop %d\n", (int) type); */
542
543 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
544
545 temp_data.position = INVALID_POS;
546
547 /* empty is apparently acceptable as a pass */
548 if (temp == 0)
549 {
550 temp_data.position = PASS_POS;
551 }
552 else if (temp == 2)
553 {
554 temp_data.position = sgf_to_pos (buffer);
555 }
556 else
557 {
558 DEBUGF ("invalid move position read in, of wrong size!\n");
559 }
560
561
562 if (temp_data.position != INVALID_POS)
563 {
564 add_prop_sgf (current_node, type, temp_data);
565 }
566
567 return;
568 }
569 else if (type == PROP_ADD_BLACK ||
570 type == PROP_ADD_WHITE ||
571 type == PROP_ADD_EMPTY ||
572 type == PROP_CIRCLE ||
573 type == PROP_SQUARE ||
574 type == PROP_TRIANGLE ||
575 type == PROP_DIM || type == PROP_MARK || type == PROP_SELECTED)
576 {
577 /* DEBUGF("add prop %d\n", (int) type); */
578
579 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
580 if (temp == 2)
581 {
582 temp_data.position = sgf_to_pos (buffer);
583
584 if (temp_data.position != INVALID_POS &&
585 temp_data.position != PASS_POS)
586 {
587 add_prop_sgf (current_node, type, temp_data);
588 }
589 }
590 else if (temp == 5)
591 {
592 /* example: "ab:cd", two positions separated by a colon */
593 temp_pos_ul = sgf_to_pos (buffer);
594 temp_pos_br = sgf_to_pos (&(buffer[3]));
595
596 if (!on_board (temp_pos_ul) || !on_board (temp_pos_br) ||
597 buffer[2] != ':')
598 {
599 DEBUGF ("invalid range value!\n");
600 }
601
602 do_range (type, temp_pos_ul, temp_pos_br);
603 }
604 else
605 {
606 DEBUGF ("invalid position or range read in. wrong size!\n");
607 }
608 return;
609 }
610 else if (type == PROP_LABEL)
611 {
612 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
613
614 if (temp < 4 || buffer[2] != ':')
615 {
616 DEBUGF ("invalid LaBel property '%s'", buffer);
617 }
618 temp_data.position = sgf_to_pos (buffer);
619
620 if (!on_board (temp_data.position))
621 {
622 DEBUGF ("LaBel set on invalid position!\n");
623 }
624
625 temp_data.label_extra = buffer[3];
626
627 add_prop_sgf (current_node, type, temp_data);
628 return;
629 }
630 else if (type == PROP_GAME)
631 {
632 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
633 if (temp != 1 || buffer[0] != '1')
634 {
635 rb->splash (2 * HZ, "This isn't a Go SGF!! Parsing stopped.");
636 DEBUGF ("incorrect game type loaded!\n");
637
638 close_file (&sgf_fd);
639 }
640 }
641 else if (type == PROP_FILE_FORMAT)
642 {
643 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
644 if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4'))
645 {
646 rb->splash (2 * HZ, "Wrong SGF file version! Parsing stopped.");
647 DEBUGF ("can't handle file format %c\n", buffer[0]);
648
649 close_file (&sgf_fd);
650 }
651 }
652 else if (type == PROP_APPLICATION ||
653 type == PROP_CHARSET || type == PROP_VARIATION_TYPE)
654 {
655 /* we don't care. on output we'll write our own values for these */
656 read_prop_value (NULL, 0);
657 }
658 else if (type == PROP_SIZE)
659 {
660 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
661 if (temp == 0)
662 {
663 rb->splash (HZ, "Invalid board size specified in file.");
664 }
665 else
666 {
667 temp_width = rb->atoi (buffer);
668 while (*buffer != ':' && *buffer != '\0')
669 {
670 ++buffer;
671 }
672
673 if (*buffer != '\0')
674 {
675 ++buffer;
676 temp_height = rb->atoi (buffer);
677 }
678 else
679 {
680 temp_height = temp_width;
681 }
682
683
684 if (!set_size_board (temp_width, temp_height))
685 {
686 rb->splashf (HZ,
687 "Board too big/small! (%dx%d) Stopping parse.",
688 temp_width, temp_height);
689 close_file (&sgf_fd);
690 }
691 else
692 {
693 clear_board ();
694 }
695 }
696 }
697 else if (type == PROP_KOMI)
698 {
699 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
700
701 if (temp == 0)
702 {
703 header.komi = 0;
704 DEBUGF ("invalid komi specification. setting to zero\n");
705 }
706 else
707 {
708 header.komi = rb->atoi (buffer) << 1;
709 while (*buffer != '.' && *buffer != ',' && *buffer != '\0')
710 {
711 ++buffer;
712 }
713
714 if (buffer != '\0')
715 {
716 ++buffer;
717
718 if (*buffer == 0)
719 {
720 /* do nothing */
721 }
722 else if (*buffer >= '1' && *buffer <= '9')
723 {
724 header.komi += 1;
725 }
726 else
727 {
728 if (*buffer != '0')
729 {
730 DEBUGF ("extra characters after komi value!\n");
731 }
732 }
733 }
734 }
735 }
736 else if (type == PROP_BLACK_NAME ||
737 type == PROP_WHITE_NAME ||
738 type == PROP_BLACK_RANK ||
739 type == PROP_WHITE_RANK ||
740 type == PROP_BLACK_TEAM ||
741 type == PROP_WHITE_TEAM ||
742 type == PROP_DATE ||
743 type == PROP_ROUND ||
744 type == PROP_EVENT ||
745 type == PROP_PLACE ||
746 type == PROP_OVERTIME ||
747 type == PROP_RESULT || type == PROP_RULESET)
748 {
749 if (!get_header_string_and_size
750 (&header, type, &temp_buffer, &temp_size))
751 {
752 rb->splash (5 * HZ,
753 "Error getting header string. Report this.");
754 }
755 else
756 {
757 temp = read_prop_value (temp_buffer, temp_size - 1);
758#if 0
759 DEBUGF ("read %d bytes into header for type: %d\n", temp, type);
760 DEBUGF ("data: %s\n", temp_buffer);
761#endif
762 }
763 }
764 else if (type == PROP_TIME_LIMIT)
765 {
766 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
767 header.time_limit = rb->atoi (buffer);
768 DEBUGF ("setting time: %d (%s)\n", header.time_limit, buffer);
769 }
770 else if (type == PROP_HANDICAP)
771 {
772 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
773 if (start_node == tree_head)
774 {
775 if (rb->atoi (buffer) >= 2)
776 {
777 start_node = current_node;
778 temp_data.number = header.handicap = rb->atoi (buffer);
779 add_prop_sgf (current_node, type, temp_data);
780 DEBUGF ("setting handicap: %d\n", header.handicap);
781 }
782 else
783 {
784 DEBUGF ("invalid HAndicap prop. ignoring\n");
785 }
786 }
787 else
788 {
789 rb->splash (HZ, "extraneous HAndicap prop present in file!\n");
790 }
791 }
792 else
793 {
794 DEBUGF ("UNHANDLED PROP TYPE!!!\n");
795 rb->splash (3 * HZ,
796 "A SGF prop was not dealt with. Please report this");
797 read_prop_value (NULL, 0);
798 }
799}
800
801
802
803/* upper-left and bottom right */
804static void
805do_range (enum prop_type_t type, unsigned short ul, unsigned short br)
806{
807 /* this code is overly general and accepts ranges even if ul and br
808 aren't the required corners it's easier doing that that failing if
809 the input is bad */
810
811 bool x_reverse = false;
812 bool y_reverse = false;
813 union prop_data_t temp_data;
814
815 if (I (br) < I (ul))
816 {
817 x_reverse = true;
818 }
819
820 if (J (br) < J (ul))
821 {
822 y_reverse = true;
823 }
824
825 int x, y;
826 for (x = I (ul);
827 x_reverse ? (x >= I (br)) : (x <= I (br)); x_reverse ? --x : ++x)
828 {
829 for (y = J (ul);
830 y_reverse ? (y >= J (br)) : (y <= J (br)); y_reverse ? --y : ++y)
831 {
832 temp_data.position = POS (x, y);
833
834 DEBUGF ("adding %d %d for range (type %d)\n",
835 I (temp_data.position), J (temp_data.position), type);
836 add_prop_sgf (current_node, type, temp_data);
837 }
838 }
839}
840
841
842
843static unsigned short
844sgf_to_pos (char *buffer)
845{
846 if (buffer[0] == 't' && buffer[1] == 't')
847 {
848 return PASS_POS;
849 }
850 else if (buffer[0] < 'a' || buffer[0] > 'z' ||
851 buffer[1] < 'a' || buffer[1] > 'z')
852 {
853 return INVALID_POS;
854 }
855 return POS (buffer[0] - 'a', buffer[1] - 'a');
856}
857