FS#12756 by Marek Salaba - update Czech translation
[maemo-rb.git] / apps / plugins / goban / sgf_parse.c
bloba2b11cf490e7d9c124a1ed589935471369719572
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
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"
30 static void handle_prop_value (enum prop_type_t type);
31 static int read_prop_value (char *buffer, size_t buffer_size);
32 static void do_range (enum prop_type_t type, unsigned short ul,
33 unsigned short br);
34 static void parse_prop (void);
35 static void parse_node (void);
36 static enum prop_type_t parse_prop_type (void);
38 static unsigned short sgf_to_pos (char *buffer);
40 bool
41 parse_sgf (const char *filename)
43 int saved = current_node;
45 /* for parsing */
46 int first_handle = 0; /* first node in the branch */
47 int file_position = 0;
49 int temp;
51 close_file (&sgf_fd);
53 sgf_fd = rb->open (filename, O_RDONLY);
55 if (sgf_fd < 0)
57 return false;
60 current_node = start_node;
62 if (current_node < 0)
64 current_node = saved;
65 return false;
68 empty_stack (&parse_stack);
70 /* seek to the first '(' */
71 while (peek_char_no_whitespace (sgf_fd) != '(')
73 if (read_char_no_whitespace (sgf_fd) == -1)
75 DEBUGF ("end of file or error before we found a '('\n");
76 current_node = saved;
77 return false;
81 push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
82 push_int_stack (&parse_stack, current_node);
84 while (pop_int_stack (&parse_stack, &first_handle) &&
85 pop_int_stack (&parse_stack, &file_position))
87 /* DEBUGF("poped off %d\n", file_position); */
89 rb->yield ();
91 current_node = first_handle;
93 if (file_position == -1)
95 temp = read_char_no_whitespace (sgf_fd);
96 if (temp != '(')
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;
104 else
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);
111 else
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);
117 rb->lseek (sgf_fd, file_position, SEEK_SET);
120 /* we're at the start of a gametree here, right at the '(' */
121 temp = read_char_no_whitespace (sgf_fd);
123 if (temp != '(')
125 DEBUGF ("start of gametree doesn't have a '('!\n");
126 current_node = saved;
127 return false;
131 while (1)
133 temp = peek_char_no_whitespace (sgf_fd);
134 /* DEBUGF("||| %d, %c\n", absolute_position(), (char) temp); */
136 if (temp == ';')
138 /* fill the tree_head node before moving on */
139 if (current_node != tree_head ||
140 get_node (current_node)->props >= 0)
142 int temp = add_child_sgf (NULL);
144 if (temp >= 0)
146 current_node = temp;
148 else
150 rb->splash (2 * HZ, "Out of memory while parsing!");
151 return false;
156 read_char_no_whitespace (sgf_fd);
157 parse_node ();
159 else if (temp == ')')
161 /* finished this gametree */
163 /* we want to end one past the ')', so eat it up: */
164 read_char_no_whitespace (sgf_fd);
165 break;
167 else if (temp == '(')
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);
175 break;
177 else if (temp == -1)
179 break;
181 else
183 DEBUGF ("extra characters found while parsing: %c\n", temp);
184 /* skip the extras i guess */
185 read_char_no_whitespace (sgf_fd);
190 current_node = get_node (tree_head)->next;
191 while (current_node >= 0 && get_node (current_node)->props < 0)
193 temp = current_node; /* to be freed later */
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)
200 get_node (current_node)->prev = tree_head;
202 struct prop_t *loop_prop =
203 get_prop (get_node (current_node)->props);
205 while (loop_prop != 0)
207 if (loop_prop->type == PROP_VARIATION)
209 get_node (loop_prop->data.number)->prev = tree_head;
211 else
213 /* all of the variations have to be up front, so we
214 can quit here */
215 break;
217 loop_prop = get_prop (loop_prop->next);
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)
226 start_node = saved = tree_head;
228 free_storage_sgf (temp);
230 current_node = get_node (tree_head)->next;
233 current_node = saved;
236 /* DEBUGF("got past!\n"); */
237 close_file (&sgf_fd);
238 return true;
242 static void
243 parse_node (void)
245 int temp;
247 while (1)
249 temp = peek_char_no_whitespace (sgf_fd);
251 if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
253 return;
255 else
257 parse_prop ();
264 int start_of_prop = 0;
265 static void
266 parse_prop (void)
268 enum prop_type_t temp_type = PROP_INVALID;
269 int temp;
272 while (1)
274 temp = peek_char_no_whitespace (sgf_fd);
276 if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
278 return;
280 else if (temp == '[')
282 handle_prop_value (temp_type);
284 else
286 start_of_prop = rb->lseek (sgf_fd, 0, SEEK_CUR);
287 temp_type = parse_prop_type ();
292 static enum prop_type_t
293 parse_prop_type (void)
295 char buffer[3];
296 int pos = 0;
297 int temp;
299 rb->memset (buffer, 0, sizeof (buffer));
301 while (1)
303 temp = peek_char_no_whitespace (sgf_fd);
305 if (temp == ';' || temp == '[' || temp == '(' ||
306 temp == -1 || temp == ')')
308 if (pos == 1 || pos == 2)
310 break;
312 else
314 return PROP_INVALID;
317 else if (temp >= 'A' && temp <= 'Z')
319 buffer[pos++] = temp;
321 if (pos == 2)
323 read_char_no_whitespace (sgf_fd);
324 break;
328 temp = read_char_no_whitespace (sgf_fd);
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)
340 failed = true;
341 read_char_no_whitespace (sgf_fd);
344 if (failed)
346 return PROP_INVALID;
349 int i;
350 for (i = 0; i < PROP_NAMES_SIZE; ++i)
352 if (rb->strcmp (buffer, prop_names[i]) == 0)
354 return (enum prop_type_t) i;
357 return PROP_INVALID;
360 static int
361 read_prop_value (char *buffer, size_t buffer_size)
363 bool escaped = false;
364 int temp;
365 int bytes_read = 0;
367 /* make it a string, the lazy way */
368 rb->memset (buffer, 0, buffer_size);
369 --buffer_size;
371 if (peek_char (sgf_fd) == '[')
373 read_char (sgf_fd);
376 while (1)
378 temp = read_char (sgf_fd);
379 if (temp == ']' && !escaped)
381 return bytes_read;
383 else if (temp == '\\')
385 if (escaped)
387 if (buffer && buffer_size)
389 *(buffer++) = temp;
390 ++bytes_read;
391 --buffer_size;
394 escaped = !escaped;
396 else if (temp == -1)
398 return bytes_read;
400 else
402 escaped = false;
403 if (buffer && buffer_size)
405 *(buffer++) = temp;
406 ++bytes_read;
407 --buffer_size;
413 static void
414 handle_prop_value (enum prop_type_t type)
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
419 (this buffer is only used for them, things such as white and black
420 player names are stored in different buffers) */
422 /* make it a little bigger for other random crap, like reading in time
424 #define PROP_HANDLER_BUFFER_SIZE 16
426 char real_buffer[PROP_HANDLER_BUFFER_SIZE];
427 char *buffer = real_buffer;
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;
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)
448 header_marked = true;
450 temp_data.number = 0; /* meaningless */
452 /* don't add more than one, so just set it if we found one already
454 add_or_set_prop_sgf (current_node, PROP_ROOT_PROPS, temp_data);
458 if (!is_handled_sgf (type) || type == PROP_COMMENT)
460 /* DEBUGF("unhandled prop %d\n", (int) type); */
461 rb->lseek (sgf_fd, start_of_prop, SEEK_SET);
463 temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR);
464 /* absolute_position(&unhandled_prop_list); */
466 add_prop_sgf (current_node,
467 type == PROP_COMMENT ? PROP_COMMENT :
468 PROP_GENERIC_UNHANDLED, temp_data);
470 got_value = false;
471 while (!done)
473 temp = peek_char (sgf_fd);
475 switch (temp)
477 case -1:
478 done = true;
479 break;
481 case '\\':
482 if (got_value && !in_prop_value)
484 done = true;
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)
496 in_prop_value = false;
498 escaped = false;
499 break;
500 case ')':
501 case '(':
502 case ';':
503 if (!in_prop_value)
505 done = true;
507 escaped = false;
508 break;
509 default:
510 if (got_value && !in_prop_value)
512 if (!is_whitespace (temp))
514 done = true;
517 escaped = false;
518 break;
521 if (done)
523 write_char (unhandled_fd, ';');
525 else
527 /* don't write out-of-prop whitespace */
528 if (in_prop_value || !is_whitespace (temp))
530 write_char (unhandled_fd, (char) temp);
533 read_char (sgf_fd);
538 return;
540 else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE)
542 /* DEBUGF("move prop %d\n", (int) type); */
544 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
546 temp_data.position = INVALID_POS;
548 /* empty is apparently acceptable as a pass */
549 if (temp == 0)
551 temp_data.position = PASS_POS;
553 else if (temp == 2)
555 temp_data.position = sgf_to_pos (buffer);
557 else
559 DEBUGF ("invalid move position read in, of wrong size!\n");
563 if (temp_data.position != INVALID_POS)
565 add_prop_sgf (current_node, type, temp_data);
568 return;
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)
578 /* DEBUGF("add prop %d\n", (int) type); */
580 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
581 if (temp == 2)
583 temp_data.position = sgf_to_pos (buffer);
585 if (temp_data.position != INVALID_POS &&
586 temp_data.position != PASS_POS)
588 add_prop_sgf (current_node, type, temp_data);
591 else if (temp == 5)
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]));
597 if (!on_board (temp_pos_ul) || !on_board (temp_pos_br) ||
598 buffer[2] != ':')
600 DEBUGF ("invalid range value!\n");
603 do_range (type, temp_pos_ul, temp_pos_br);
605 else
607 DEBUGF ("invalid position or range read in. wrong size!\n");
609 return;
611 else if (type == PROP_LABEL)
613 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
615 if (temp < 4 || buffer[2] != ':')
617 DEBUGF ("invalid LaBel property '%s'", buffer);
619 temp_data.position = sgf_to_pos (buffer);
621 if (!on_board (temp_data.position))
623 DEBUGF ("LaBel set on invalid position!\n");
626 temp_data.label_extra = buffer[3];
628 add_prop_sgf (current_node, type, temp_data);
629 return;
631 else if (type == PROP_GAME)
633 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
634 if (temp != 1 || buffer[0] != '1')
636 rb->splash (2 * HZ, "This isn't a Go SGF!! Parsing stopped.");
637 DEBUGF ("incorrect game type loaded!\n");
639 close_file (&sgf_fd);
642 else if (type == PROP_FILE_FORMAT)
644 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
645 if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4'))
647 rb->splash (2 * HZ, "Wrong SGF file version! Parsing stopped.");
648 DEBUGF ("can't handle file format %c\n", buffer[0]);
650 close_file (&sgf_fd);
653 else if (type == PROP_APPLICATION ||
654 type == PROP_CHARSET || type == PROP_VARIATION_TYPE)
656 /* we don't care. on output we'll write our own values for these */
657 read_prop_value (NULL, 0);
659 else if (type == PROP_SIZE)
661 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
662 if (temp == 0)
664 rb->splash (HZ, "Invalid board size specified in file.");
666 else
668 temp_width = rb->atoi (buffer);
669 while (*buffer != ':' && *buffer != '\0')
671 ++buffer;
674 if (*buffer != '\0')
676 ++buffer;
677 temp_height = rb->atoi (buffer);
679 else
681 temp_height = temp_width;
685 if (!set_size_board (temp_width, temp_height))
687 rb->splashf (HZ,
688 "Board too big/small! (%dx%d) Stopping parse.",
689 temp_width, temp_height);
690 close_file (&sgf_fd);
692 else
694 clear_board ();
698 else if (type == PROP_KOMI)
700 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
702 if (temp == 0)
704 header.komi = 0;
705 DEBUGF ("invalid komi specification. setting to zero\n");
707 else
709 header.komi = rb->atoi (buffer) << 1;
710 while (*buffer != '.' && *buffer != ',' && *buffer != '\0')
712 ++buffer;
715 if (buffer != '\0')
717 ++buffer;
719 if (*buffer == 0)
721 /* do nothing */
723 else if (*buffer >= '1' && *buffer <= '9')
725 header.komi += 1;
727 else
729 if (*buffer != '0')
731 DEBUGF ("extra characters after komi value!\n");
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)
750 if (!get_header_string_and_size
751 (&header, type, &temp_buffer, &temp_size))
753 rb->splash (5 * HZ,
754 "Error getting header string. Report this.");
756 else
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
765 else if (type == PROP_TIME_LIMIT)
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);
771 else if (type == PROP_HANDICAP)
773 temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
774 if (start_node == tree_head)
776 if (rb->atoi (buffer) >= 2)
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);
783 else
785 DEBUGF ("invalid HAndicap prop. ignoring\n");
788 else
790 rb->splash (HZ, "extraneous HAndicap prop present in file!\n");
793 else
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);
804 /* upper-left and bottom right */
805 static void
806 do_range (enum prop_type_t type, unsigned short ul, unsigned short br)
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 */
812 bool x_reverse = false;
813 bool y_reverse = false;
814 union prop_data_t temp_data;
816 if (I (br) < I (ul))
818 x_reverse = true;
821 if (J (br) < J (ul))
823 y_reverse = true;
826 int x, y;
827 for (x = I (ul);
828 x_reverse ? (x >= I (br)) : (x <= I (br)); x_reverse ? --x : ++x)
830 for (y = J (ul);
831 y_reverse ? (y >= J (br)) : (y <= J (br)); y_reverse ? --y : ++y)
833 temp_data.position = POS (x, y);
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);
844 static unsigned short
845 sgf_to_pos (char *buffer)
847 if (buffer[0] == 't' && buffer[1] == 't')
849 return PASS_POS;
851 else if (buffer[0] < 'a' || buffer[0] > 'z' ||
852 buffer[1] < 'a' || buffer[1] > 'z')
854 return INVALID_POS;
856 return POS (buffer[0] - 'a', buffer[1] - 'a');