rockbox/apps/plugins/goban/sgf_parse.c

859 lines
23 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "goban.h"
#include "sgf_parse.h"
#include "sgf.h"
#include "sgf_storage.h"
#include "util.h"
#include "board.h"
#include "game.h"
static void handle_prop_value (enum prop_type_t type);
static int read_prop_value (char *buffer, size_t buffer_size);
static void do_range (enum prop_type_t type, unsigned short ul,
unsigned short br);
static void parse_prop (void);
static void parse_node (void);
static enum prop_type_t parse_prop_type (void);
static unsigned short sgf_to_pos (char *buffer);
bool
parse_sgf (const char *filename)
{
int saved = current_node;
/* for parsing */
int first_handle = 0; /* first node in the branch */
int file_position = 0;
int temp;
close_file (&sgf_fd);
sgf_fd = rb->open (filename, O_RDONLY);
if (sgf_fd < 0)
{
return false;
}
current_node = start_node;
if (current_node < 0)
{
current_node = saved;
return false;
}
empty_stack (&parse_stack);
/* seek to the first '(' */
while (peek_char_no_whitespace (sgf_fd) != '(')
{
if (read_char_no_whitespace (sgf_fd) == -1)
{
DEBUGF ("end of file or error before we found a '('\n");
current_node = saved;
return false;
}
}
push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
push_int_stack (&parse_stack, current_node);
while (pop_int_stack (&parse_stack, &first_handle) &&
pop_int_stack (&parse_stack, &file_position))
{
/* DEBUGF("poped off %d\n", file_position); */
rb->yield ();
current_node = first_handle;
if (file_position == -1)
{
temp = read_char_no_whitespace (sgf_fd);
if (temp != '(')
{
/* we're here because there may have been a sibling after
another gametree that was handled, but there's no '(',
so there wasnt' a sibling, so just go on to any more
gametrees in the stack */
continue;
}
else
{
/* there may be more siblings after we process this one */
push_int_stack (&parse_stack, -1);
push_int_stack (&parse_stack, first_handle);
}
}
else
{
/* check for a sibling after we finish with this node */
push_int_stack (&parse_stack, -1);
push_int_stack (&parse_stack, first_handle);
rb->lseek (sgf_fd, file_position, SEEK_SET);
/* we're at the start of a gametree here, right at the '(' */
temp = read_char_no_whitespace (sgf_fd);
if (temp != '(')
{
DEBUGF ("start of gametree doesn't have a '('!\n");
current_node = saved;
return false;
}
}
while (1)
{
temp = peek_char_no_whitespace (sgf_fd);
/* DEBUGF("||| %d, %c\n", absolute_position(), (char) temp); */
if (temp == ';')
{
/* fill the tree_head node before moving on */
if (current_node != tree_head ||
get_node (current_node)->props >= 0)
{
int temp = add_child_sgf (NULL);
if (temp >= 0)
{
current_node = temp;
}
else
{
rb->splash (2 * HZ, "Out of memory while parsing!");
return false;
}
}
read_char_no_whitespace (sgf_fd);
parse_node ();
}
else if (temp == ')')
{
/* finished this gametree */
/* we want to end one past the ')', so eat it up: */
read_char_no_whitespace (sgf_fd);
break;
}
else if (temp == '(')
{
/*
DEBUGF ("adding %d\n", (int) rb->lseek (sgf_fd, 0,
SEEK_CUR)); */
push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
push_int_stack (&parse_stack, current_node);
break;
}
else if (temp == -1)
{
break;
}
else
{
DEBUGF ("extra characters found while parsing: %c\n", temp);
/* skip the extras i guess */
read_char_no_whitespace (sgf_fd);
}
}
}
current_node = get_node (tree_head)->next;
while (current_node >= 0 && get_node (current_node)->props < 0)
{
temp = current_node; /* to be freed later */
/* update the ->prev pointed on all branches of the next node */
current_node = get_node (current_node)->next;
/* DEBUGF("trying to set prev for branch %d\n", current_node); */
if (current_node >= 0)
{
get_node (current_node)->prev = tree_head;
struct prop_t *loop_prop =
get_prop (get_node (current_node)->props);
while (loop_prop != 0)
{
if (loop_prop->type == PROP_VARIATION)
{
get_node (loop_prop->data.number)->prev = tree_head;
}
else
{
/* all of the variations have to be up front, so we
can quit here */
break;
}
loop_prop = get_prop (loop_prop->next);
}
}
/* update the tree head */
get_node (tree_head)->next = get_node (temp)->next;
/* DEBUGF("freeing %d %d %d\n", temp, start_node, saved); */
if (start_node == temp || saved == temp)
{
start_node = saved = tree_head;
}
free_storage_sgf (temp);
current_node = get_node (tree_head)->next;
}
current_node = saved;
/* DEBUGF("got past!\n"); */
close_file (&sgf_fd);
return true;
}
static void
parse_node (void)
{
int temp;
while (1)
{
temp = peek_char_no_whitespace (sgf_fd);
if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
{
return;
}
else
{
parse_prop ();
}
}
}
int start_of_prop = 0;
static void
parse_prop (void)
{
enum prop_type_t temp_type = PROP_INVALID;
int temp;
while (1)
{
temp = peek_char_no_whitespace (sgf_fd);
if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
{
return;
}
else if (temp == '[')
{
handle_prop_value (temp_type);
}
else
{
start_of_prop = rb->lseek (sgf_fd, 0, SEEK_CUR);
temp_type = parse_prop_type ();
}
}
}
static enum prop_type_t
parse_prop_type (void)
{
char buffer[3];
int pos = 0;
int temp;
rb->memset (buffer, 0, sizeof (buffer));
while (1)
{
temp = peek_char_no_whitespace (sgf_fd);
if (temp == ';' || temp == '[' || temp == '(' ||
temp == -1 || temp == ')')
{
if (pos == 1 || pos == 2)
{
break;
}
else
{
return PROP_INVALID;
}
}
else if (temp >= 'A' && temp <= 'Z')
{
buffer[pos++] = temp;
if (pos == 2)
{
read_char_no_whitespace (sgf_fd);
break;
}
}
temp = read_char_no_whitespace (sgf_fd);
}
/* check if we're still reading a prop name, in which case we fail
(but first we want to eat up the rest of the prop name) */
bool failed = false;
while (peek_char_no_whitespace (sgf_fd) != ';' &&
peek_char_no_whitespace (sgf_fd) != '[' &&
peek_char_no_whitespace (sgf_fd) != '(' &&
peek_char_no_whitespace (sgf_fd) != '}' &&
peek_char_no_whitespace (sgf_fd) != -1)
{
failed = true;
read_char_no_whitespace (sgf_fd);
}
if (failed)
{
return PROP_INVALID;
}
int i;
for (i = 0; i < PROP_NAMES_SIZE; ++i)
{
if (rb->strcmp (buffer, prop_names[i]) == 0)
{
return (enum prop_type_t) i;
}
}
return PROP_INVALID;
}
static int
read_prop_value (char *buffer, size_t buffer_size)
{
bool escaped = false;
int temp;
int bytes_read = 0;
/* make it a string, the lazy way */
rb->memset (buffer, 0, buffer_size);
--buffer_size;
if (peek_char (sgf_fd) == '[')
{
read_char (sgf_fd);
}
while (1)
{
temp = read_char (sgf_fd);
if (temp == ']' && !escaped)
{
return bytes_read;
}
else if (temp == '\\')
{
if (escaped)
{
if (buffer && buffer_size)
{
*(buffer++) = temp;
++bytes_read;
--buffer_size;
}
}
escaped = !escaped;
}
else if (temp == -1)
{
return bytes_read;
}
else
{
escaped = false;
if (buffer && buffer_size)
{
*(buffer++) = temp;
++bytes_read;
--buffer_size;
}
}
}
}
static void
handle_prop_value (enum prop_type_t type)
{
/* max size of generically supported prop values is 6, which is 5 for
a point range ab:cd and one for the \0
(this buffer is only used for them, things such as white and black
player names are stored in different buffers) */
/* make it a little bigger for other random crap, like reading in time
*/
#define PROP_HANDLER_BUFFER_SIZE 16
char real_buffer[PROP_HANDLER_BUFFER_SIZE];
char *buffer = real_buffer;
int temp;
union prop_data_t temp_data;
bool in_prop_value = false;
bool escaped = false;
bool done = false;
int temp_width, temp_height;
unsigned short temp_pos_ul, temp_pos_br;
int temp_size;
char *temp_buffer;
bool got_value;
/* special extra handling for root properties, set a marker telling us
the right place to spit the values out in output_sgf */
if (type == PROP_GAME ||
type == PROP_APPLICATION ||
type == PROP_CHARSET ||
type == PROP_SIZE ||
type == PROP_FILE_FORMAT || type == PROP_VARIATION_TYPE)
{
header_marked = true;
temp_data.number = 0; /* meaningless */
/* don't add more than one, so just set it if we found one already
*/
add_or_set_prop_sgf (current_node, PROP_ROOT_PROPS, temp_data);
}
if (!is_handled_sgf (type) || type == PROP_COMMENT)
{
/* DEBUGF("unhandled prop %d\n", (int) type); */
rb->lseek (sgf_fd, start_of_prop, SEEK_SET);
temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR);
/* absolute_position(&unhandled_prop_list); */
add_prop_sgf (current_node,
type == PROP_COMMENT ? PROP_COMMENT :
PROP_GENERIC_UNHANDLED, temp_data);
got_value = false;
while (!done)
{
temp = peek_char (sgf_fd);
switch (temp)
{
case -1:
done = true;
break;
case '\\':
if (got_value && !in_prop_value)
{
done = true;
}
escaped = !escaped;
break;
case '[':
escaped = false;
in_prop_value = true;
got_value = true;
break;
case ']':
if (!escaped)
{
in_prop_value = false;
}
escaped = false;
break;
case ')':
case '(':
case ';':
if (!in_prop_value)
{
done = true;
}
escaped = false;
break;
default:
if (got_value && !in_prop_value)
{
if (!is_whitespace (temp))
{
done = true;
}
}
escaped = false;
break;
};
if (done)
{
write_char (unhandled_fd, ';');
}
else
{
/* don't write out-of-prop whitespace */
if (in_prop_value || !is_whitespace (temp))
{
write_char (unhandled_fd, (char) temp);
}
read_char (sgf_fd);
}
}
return;
}
else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE)
{
/* DEBUGF("move prop %d\n", (int) type); */
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
temp_data.position = INVALID_POS;
/* empty is apparently acceptable as a pass */
if (temp == 0)
{
temp_data.position = PASS_POS;
}
else if (temp == 2)
{
temp_data.position = sgf_to_pos (buffer);
}
else
{
DEBUGF ("invalid move position read in, of wrong size!\n");
}
if (temp_data.position != INVALID_POS)
{
add_prop_sgf (current_node, type, temp_data);
}
return;
}
else if (type == PROP_ADD_BLACK ||
type == PROP_ADD_WHITE ||
type == PROP_ADD_EMPTY ||
type == PROP_CIRCLE ||
type == PROP_SQUARE ||
type == PROP_TRIANGLE ||
type == PROP_DIM || type == PROP_MARK || type == PROP_SELECTED)
{
/* DEBUGF("add prop %d\n", (int) type); */
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (temp == 2)
{
temp_data.position = sgf_to_pos (buffer);
if (temp_data.position != INVALID_POS &&
temp_data.position != PASS_POS)
{
add_prop_sgf (current_node, type, temp_data);
}
}
else if (temp == 5)
{
/* example: "ab:cd", two positions separated by a colon */
temp_pos_ul = sgf_to_pos (buffer);
temp_pos_br = sgf_to_pos (&(buffer[3]));
if (!on_board (temp_pos_ul) || !on_board (temp_pos_br) ||
buffer[2] != ':')
{
DEBUGF ("invalid range value!\n");
}
do_range (type, temp_pos_ul, temp_pos_br);
}
else
{
DEBUGF ("invalid position or range read in. wrong size!\n");
}
return;
}
else if (type == PROP_LABEL)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (temp < 4 || buffer[2] != ':')
{
DEBUGF ("invalid LaBel property '%s'", buffer);
}
temp_data.position = sgf_to_pos (buffer);
if (!on_board (temp_data.position))
{
DEBUGF ("LaBel set on invalid position!\n");
}
temp_data.label_extra = buffer[3];
add_prop_sgf (current_node, type, temp_data);
return;
}
else if (type == PROP_GAME)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (temp != 1 || buffer[0] != '1')
{
rb->splash (2 * HZ, "This isn't a Go SGF!! Parsing stopped.");
DEBUGF ("incorrect game type loaded!\n");
close_file (&sgf_fd);
}
}
else if (type == PROP_FILE_FORMAT)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4'))
{
rb->splash (2 * HZ, "Wrong SGF file version! Parsing stopped.");
DEBUGF ("can't handle file format %c\n", buffer[0]);
close_file (&sgf_fd);
}
}
else if (type == PROP_APPLICATION ||
type == PROP_CHARSET || type == PROP_VARIATION_TYPE)
{
/* we don't care. on output we'll write our own values for these */
read_prop_value (NULL, 0);
}
else if (type == PROP_SIZE)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (temp == 0)
{
rb->splash (HZ, "Invalid board size specified in file.");
}
else
{
temp_width = rb->atoi (buffer);
while (*buffer != ':' && *buffer != '\0')
{
++buffer;
}
if (*buffer != '\0')
{
++buffer;
temp_height = rb->atoi (buffer);
}
else
{
temp_height = temp_width;
}
if (!set_size_board (temp_width, temp_height))
{
rb->splashf (HZ,
"Board too big/small! (%dx%d) Stopping parse.",
temp_width, temp_height);
close_file (&sgf_fd);
}
else
{
clear_board ();
}
}
}
else if (type == PROP_KOMI)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (temp == 0)
{
header.komi = 0;
DEBUGF ("invalid komi specification. setting to zero\n");
}
else
{
header.komi = rb->atoi (buffer) << 1;
while (*buffer != '.' && *buffer != ',' && *buffer != '\0')
{
++buffer;
}
if (*buffer != '\0')
{
++buffer;
if (*buffer == 0)
{
/* do nothing */
}
else if (*buffer >= '1' && *buffer <= '9')
{
header.komi += 1;
}
else
{
if (*buffer != '0')
{
DEBUGF ("extra characters after komi value!\n");
}
}
}
}
}
else if (type == PROP_BLACK_NAME ||
type == PROP_WHITE_NAME ||
type == PROP_BLACK_RANK ||
type == PROP_WHITE_RANK ||
type == PROP_BLACK_TEAM ||
type == PROP_WHITE_TEAM ||
type == PROP_DATE ||
type == PROP_ROUND ||
type == PROP_EVENT ||
type == PROP_PLACE ||
type == PROP_OVERTIME ||
type == PROP_RESULT || type == PROP_RULESET)
{
if (!get_header_string_and_size
(&header, type, &temp_buffer, &temp_size))
{
rb->splash (5 * HZ,
"Error getting header string. Report this.");
}
else
{
temp = read_prop_value (temp_buffer, temp_size - 1);
#if 0
DEBUGF ("read %d bytes into header for type: %d\n", temp, type);
DEBUGF ("data: %s\n", temp_buffer);
#endif
}
}
else if (type == PROP_TIME_LIMIT)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
header.time_limit = rb->atoi (buffer);
DEBUGF ("setting time: %d (%s)\n", header.time_limit, buffer);
}
else if (type == PROP_HANDICAP)
{
temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
if (start_node == tree_head)
{
if (rb->atoi (buffer) >= 2)
{
start_node = current_node;
temp_data.number = header.handicap = rb->atoi (buffer);
add_prop_sgf (current_node, type, temp_data);
DEBUGF ("setting handicap: %d\n", header.handicap);
}
else
{
DEBUGF ("invalid HAndicap prop. ignoring\n");
}
}
else
{
rb->splash (HZ, "extraneous HAndicap prop present in file!\n");
}
}
else
{
DEBUGF ("UNHANDLED PROP TYPE!!!\n");
rb->splash (3 * HZ,
"A SGF prop was not dealt with. Please report this");
read_prop_value (NULL, 0);
}
}
/* upper-left and bottom right */
static void
do_range (enum prop_type_t type, unsigned short ul, unsigned short br)
{
/* this code is overly general and accepts ranges even if ul and br
aren't the required corners it's easier doing that that failing if
the input is bad */
bool x_reverse = false;
bool y_reverse = false;
union prop_data_t temp_data;
if (I (br) < I (ul))
{
x_reverse = true;
}
if (J (br) < J (ul))
{
y_reverse = true;
}
int x, y;
for (x = I (ul);
x_reverse ? (x >= I (br)) : (x <= I (br)); x_reverse ? --x : ++x)
{
for (y = J (ul);
y_reverse ? (y >= J (br)) : (y <= J (br)); y_reverse ? --y : ++y)
{
temp_data.position = POS (x, y);
DEBUGF ("adding %d %d for range (type %d)\n",
I (temp_data.position), J (temp_data.position), type);
add_prop_sgf (current_node, type, temp_data);
}
}
}
static unsigned short
sgf_to_pos (char *buffer)
{
if (buffer[0] == 't' && buffer[1] == 't')
{
return PASS_POS;
}
else if (buffer[0] < 'a' || buffer[0] > 'z' ||
buffer[1] < 'a' || buffer[1] > 'z')
{
return INVALID_POS;
}
return POS (buffer[0] - 'a', buffer[1] - 'a');
}