1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
23 #include "sgf_parse.h"
25 #include "sgf_storage.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
,
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
);
41 parse_sgf (const char *filename
)
43 int saved
= current_node
;
46 int first_handle
= 0; /* first node in the branch */
47 int file_position
= 0;
53 sgf_fd
= rb
->open (filename
, O_RDONLY
);
60 current_node
= start_node
;
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");
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); */
91 current_node
= first_handle
;
93 if (file_position
== -1)
95 temp
= read_char_no_whitespace (sgf_fd
);
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 */
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
);
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
);
125 DEBUGF ("start of gametree doesn't have a '('!\n");
126 current_node
= saved
;
133 temp
= peek_char_no_whitespace (sgf_fd
);
134 /* DEBUGF("||| %d, %c\n", absolute_position(), (char) 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
);
150 rb
->splash (2 * HZ
, "Out of memory while parsing!");
156 read_char_no_whitespace (sgf_fd
);
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
);
167 else if (temp
== '(')
170 DEBUGF ("adding %d\n", (int) rb->lseek (sgf_fd, 0,
172 push_int_stack (&parse_stack
, rb
->lseek (sgf_fd
, 0, SEEK_CUR
));
173 push_int_stack (&parse_stack
, current_node
);
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
;
213 /* all of the variations have to be up front, so we
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
);
249 temp
= peek_char_no_whitespace (sgf_fd
);
251 if (temp
== -1 || temp
== ')' || temp
== '(' || temp
== ';')
264 int start_of_prop
= 0;
268 enum prop_type_t temp_type
= PROP_INVALID
;
274 temp
= peek_char_no_whitespace (sgf_fd
);
276 if (temp
== -1 || temp
== ')' || temp
== '(' || temp
== ';')
280 else if (temp
== '[')
282 handle_prop_value (temp_type
);
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)
299 rb
->memset (buffer
, 0, sizeof (buffer
));
303 temp
= peek_char_no_whitespace (sgf_fd
);
305 if (temp
== ';' || temp
== '[' || temp
== '(' ||
306 temp
== -1 || temp
== ')')
308 if (pos
== 1 || pos
== 2)
317 else if (temp
>= 'A' && temp
<= 'Z')
319 buffer
[pos
++] = temp
;
323 read_char_no_whitespace (sgf_fd
);
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) */
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)
341 read_char_no_whitespace (sgf_fd
);
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
;
361 read_prop_value (char *buffer
, size_t buffer_size
)
363 bool escaped
= false;
367 /* make it a string, the lazy way */
368 rb
->memset (buffer
, 0, buffer_size
);
371 if (peek_char (sgf_fd
) == '[')
378 temp
= read_char (sgf_fd
);
379 if (temp
== ']' && !escaped
)
383 else if (temp
== '\\')
387 if (buffer
&& buffer_size
)
403 if (buffer
&& buffer_size
)
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
;
430 union prop_data_t temp_data
;
431 bool in_prop_value
= false;
432 bool escaped
= false;
434 int temp_width
, temp_height
;
435 unsigned short temp_pos_ul
, temp_pos_br
;
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
||
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
);
473 temp
= peek_char (sgf_fd
);
482 if (got_value
&& !in_prop_value
)
490 in_prop_value
= true;
496 in_prop_value
= false;
510 if (got_value
&& !in_prop_value
)
512 if (!is_whitespace (temp
))
523 write_char (unhandled_fd
, ';');
527 /* don't write out-of-prop whitespace */
528 if (in_prop_value
|| !is_whitespace (temp
))
530 write_char (unhandled_fd
, (char) temp
);
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 */
551 temp_data
.position
= PASS_POS
;
555 temp_data
.position
= sgf_to_pos (buffer
);
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
);
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
);
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
);
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
) ||
600 DEBUGF ("invalid range value!\n");
603 do_range (type
, temp_pos_ul
, temp_pos_br
);
607 DEBUGF ("invalid position or range read in. wrong size!\n");
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
);
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
);
664 rb
->splash (HZ
, "Invalid board size specified in file.");
668 temp_width
= rb
->atoi (buffer
);
669 while (*buffer
!= ':' && *buffer
!= '\0')
677 temp_height
= rb
->atoi (buffer
);
681 temp_height
= temp_width
;
685 if (!set_size_board (temp_width
, temp_height
))
688 "Board too big/small! (%dx%d) Stopping parse.",
689 temp_width
, temp_height
);
690 close_file (&sgf_fd
);
698 else if (type
== PROP_KOMI
)
700 temp
= read_prop_value (buffer
, PROP_HANDLER_BUFFER_SIZE
);
705 DEBUGF ("invalid komi specification. setting to zero\n");
709 header
.komi
= rb
->atoi (buffer
) << 1;
710 while (*buffer
!= '.' && *buffer
!= ',' && *buffer
!= '\0')
723 else if (*buffer
>= '1' && *buffer
<= '9')
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
||
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
))
754 "Error getting header string. Report this.");
758 temp
= read_prop_value (temp_buffer
, temp_size
- 1);
760 DEBUGF ("read %d bytes into header for type: %d\n", temp
, type
);
761 DEBUGF ("data: %s\n", temp_buffer
);
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
);
785 DEBUGF ("invalid HAndicap prop. ignoring\n");
790 rb
->splash (HZ
, "extraneous HAndicap prop present in file!\n");
795 DEBUGF ("UNHANDLED PROP TYPE!!!\n");
797 "A SGF prop was not dealt with. Please report this");
798 read_prop_value (NULL
, 0);
804 /* upper-left and bottom right */
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
812 bool x_reverse
= false;
813 bool y_reverse
= false;
814 union prop_data_t temp_data
;
828 x_reverse
? (x
>= I (br
)) : (x
<= I (br
)); x_reverse
? --x
: ++x
)
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')
851 else if (buffer
[0] < 'a' || buffer
[0] > 'z' ||
852 buffer
[1] < 'a' || buffer
[1] > 'z')
856 return POS (buffer
[0] - 'a', buffer
[1] - 'a');