2010-12-08 07:18:30 +00:00
|
|
|
private int str_index (string name)
|
|
|
|
{
|
|
|
|
if (name == "Event")
|
|
|
|
return 0;
|
|
|
|
else if (name == "Site")
|
|
|
|
return 1;
|
|
|
|
else if (name == "Date")
|
|
|
|
return 2;
|
|
|
|
else if (name == "Round")
|
|
|
|
return 3;
|
|
|
|
else if (name == "White")
|
|
|
|
return 4;
|
|
|
|
else if (name == "Black")
|
|
|
|
return 5;
|
|
|
|
else if (name == "Result")
|
|
|
|
return 6;
|
|
|
|
else
|
|
|
|
return 7;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int compare_tag (string name0, string name1)
|
|
|
|
{
|
|
|
|
int str_index0 = str_index (name0);
|
|
|
|
int str_index1 = str_index (name1);
|
|
|
|
|
|
|
|
/* If both not in STR then just order alphabetically */
|
|
|
|
if (str_index0 == 7 && str_index1 == 7)
|
|
|
|
return strcmp (name0, name1);
|
|
|
|
else
|
|
|
|
return str_index0 - str_index1;
|
|
|
|
}
|
|
|
|
|
2011-01-01 02:42:03 +00:00
|
|
|
public errordomain PGNError
|
|
|
|
{
|
|
|
|
LOAD_ERROR
|
|
|
|
}
|
|
|
|
|
2010-12-08 07:18:30 +00:00
|
|
|
public class PGNGame
|
|
|
|
{
|
2011-01-01 01:42:50 +00:00
|
|
|
public HashTable<string, string> tags;
|
|
|
|
public List<string> moves;
|
2010-12-08 07:18:30 +00:00
|
|
|
|
2011-01-01 03:24:41 +00:00
|
|
|
public static string RESULT_IN_PROGRESS = "*";
|
|
|
|
public static string RESULT_DRAW = "1/2-1/2";
|
|
|
|
public static string RESULT_WHITE = "1-0";
|
|
|
|
public static string RESULT_BLACK = "0-1";
|
|
|
|
|
2011-01-01 03:58:25 +00:00
|
|
|
public static string TERMINATE_ABANDONED = "abandoned";
|
|
|
|
public static string TERMINATE_ADJUDICATION = "adjudication";
|
|
|
|
public static string TERMINATE_DEATH = "death";
|
|
|
|
public static string TERMINATE_EMERGENCY = "emergency";
|
|
|
|
public static string TERMINATE_NORMAL = "normal";
|
|
|
|
public static string TERMINATE_RULES_INFRACTION = "rules infraction";
|
|
|
|
public static string TERMINATE_TIME_FORFEIT = "time forfeit";
|
|
|
|
public static string TERMINATE_UNTERMINATED = "unterminated";
|
|
|
|
|
2010-12-08 07:18:30 +00:00
|
|
|
public string event
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Event"); }
|
|
|
|
set { tags.insert ("Event", value); }
|
|
|
|
}
|
|
|
|
public string site
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Site"); }
|
|
|
|
set { tags.insert ("Site", value); }
|
|
|
|
}
|
|
|
|
public string date
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Date"); }
|
|
|
|
set { tags.insert ("Date", value); }
|
|
|
|
}
|
2011-01-09 03:25:33 +00:00
|
|
|
public string time
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Time"); }
|
|
|
|
set { tags.insert ("Time", value); }
|
|
|
|
}
|
2010-12-08 07:18:30 +00:00
|
|
|
public string round
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Round"); }
|
|
|
|
set { tags.insert ("Round", value); }
|
|
|
|
}
|
|
|
|
public string white
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("White"); }
|
|
|
|
set { tags.insert ("White", value); }
|
|
|
|
}
|
|
|
|
public string black
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Black"); }
|
|
|
|
set { tags.insert ("Black", value); }
|
|
|
|
}
|
|
|
|
public string result
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Result"); }
|
|
|
|
set { tags.insert ("Result", value); }
|
|
|
|
}
|
2010-12-27 02:31:07 +00:00
|
|
|
public string? annotator
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Annotator"); }
|
|
|
|
set { tags.insert ("Annotator", value); }
|
|
|
|
}
|
2011-01-09 03:25:33 +00:00
|
|
|
public string? time_control
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("TimeControl"); }
|
|
|
|
set { tags.insert ("TimeControl", value); }
|
|
|
|
}
|
2010-12-27 01:47:46 +00:00
|
|
|
public bool set_up
|
|
|
|
{
|
|
|
|
get { string? v = tags.lookup ("SetUp"); return v != null && v == "1" ? true : false; }
|
|
|
|
set { tags.insert ("SetUp", value ? "1" : "0"); }
|
|
|
|
}
|
|
|
|
public string? fen
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("FEN"); }
|
2010-12-27 02:31:07 +00:00
|
|
|
set { tags.insert ("FEN", value); }
|
2010-12-27 01:47:46 +00:00
|
|
|
}
|
|
|
|
public string? termination
|
|
|
|
{
|
|
|
|
get { return tags.lookup ("Termination"); }
|
|
|
|
set { tags.insert ("Termination", value); }
|
2011-01-09 03:25:33 +00:00
|
|
|
}
|
2010-12-08 07:18:30 +00:00
|
|
|
|
|
|
|
public PGNGame ()
|
|
|
|
{
|
2011-01-01 01:42:50 +00:00
|
|
|
tags = new HashTable<string, string> (str_hash, str_equal);
|
2010-12-08 07:18:30 +00:00
|
|
|
tags.insert ("Event", "?");
|
|
|
|
tags.insert ("Site", "?");
|
|
|
|
tags.insert ("Date", "????.??.??");
|
|
|
|
tags.insert ("Round", "?");
|
|
|
|
tags.insert ("White", "?");
|
|
|
|
tags.insert ("Black", "?");
|
2011-01-01 03:24:41 +00:00
|
|
|
tags.insert ("Result", PGNGame.RESULT_IN_PROGRESS);
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
|
|
|
|
2011-07-01 10:58:03 +00:00
|
|
|
public string escape (string value)
|
|
|
|
{
|
|
|
|
var a = value.replace ("\\", "\\\\");
|
|
|
|
return a.replace ("\"", "\\\"");
|
|
|
|
}
|
|
|
|
|
2011-01-01 03:24:41 +00:00
|
|
|
public void write (File file) throws Error
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
2011-01-01 03:24:41 +00:00
|
|
|
var data = new StringBuilder ();
|
|
|
|
|
2010-12-08 07:18:30 +00:00
|
|
|
var keys = tags.get_keys ();
|
2011-01-01 01:42:50 +00:00
|
|
|
keys.sort ((CompareFunc) compare_tag);
|
2010-12-08 07:18:30 +00:00
|
|
|
foreach (var key in keys)
|
2011-07-01 10:58:03 +00:00
|
|
|
data.append ("[%s \"%s\"]\n".printf (key, escape (tags.lookup (key))));
|
2011-01-01 03:24:41 +00:00
|
|
|
data.append ("\n");
|
2010-12-08 07:18:30 +00:00
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
foreach (string move in moves)
|
|
|
|
{
|
|
|
|
if (i % 2 == 0)
|
2011-01-01 03:24:41 +00:00
|
|
|
data.append ("%d. ".printf (i / 2 + 1));
|
|
|
|
data.append (move);
|
|
|
|
data.append (" ");
|
2010-12-08 07:18:30 +00:00
|
|
|
i++;
|
|
|
|
}
|
2011-01-01 03:24:41 +00:00
|
|
|
data.append (result);
|
|
|
|
data.append ("\n");
|
2010-12-08 07:18:30 +00:00
|
|
|
|
2012-02-05 06:05:02 +00:00
|
|
|
file.replace_contents (data.str.data, null, false, FileCreateFlags.NONE, null);
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum State
|
|
|
|
{
|
2011-02-22 01:34:19 +00:00
|
|
|
TAGS,
|
|
|
|
MOVE_TEXT,
|
2010-12-08 07:18:30 +00:00
|
|
|
LINE_COMMENT,
|
|
|
|
BRACE_COMMENT,
|
|
|
|
TAG_START,
|
|
|
|
TAG_NAME,
|
|
|
|
PRE_TAG_VALUE,
|
|
|
|
TAG_VALUE,
|
|
|
|
POST_TAG_VALUE,
|
|
|
|
SYMBOL,
|
2011-02-22 01:34:19 +00:00
|
|
|
PERIOD,
|
2010-12-08 07:18:30 +00:00
|
|
|
NAG,
|
|
|
|
ERROR
|
|
|
|
}
|
|
|
|
|
|
|
|
public class PGN
|
|
|
|
{
|
2011-01-01 01:42:50 +00:00
|
|
|
public List<PGNGame> games;
|
2010-12-08 07:18:30 +00:00
|
|
|
|
|
|
|
public PGN ()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2011-02-22 01:34:19 +00:00
|
|
|
public PGN.from_string (string data) throws PGNError
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
// FIXME: Feed through newline at end to make sure parsing complete
|
2011-02-22 01:34:19 +00:00
|
|
|
|
|
|
|
State state = State.TAGS, home_state = State.TAGS;
|
2010-12-08 07:18:30 +00:00
|
|
|
PGNGame game = new PGNGame ();
|
|
|
|
bool in_escape = false;
|
|
|
|
size_t token_start = 0, line_offset = 0;
|
|
|
|
string tag_name = "";
|
|
|
|
StringBuilder tag_value = new StringBuilder ();
|
|
|
|
int line = 1;
|
|
|
|
int rav_level = 0;
|
2011-02-22 01:34:19 +00:00
|
|
|
for (size_t offset = 0; offset <= data.length; offset++)
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
2011-02-22 01:34:19 +00:00
|
|
|
unichar c = data[(long) offset];
|
|
|
|
|
2010-12-08 07:18:30 +00:00
|
|
|
if (c == '\n')
|
|
|
|
{
|
|
|
|
line++;
|
|
|
|
line_offset = offset + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (state)
|
|
|
|
{
|
2011-02-22 01:34:19 +00:00
|
|
|
case State.TAGS:
|
|
|
|
home_state = State.TAGS;
|
2010-12-08 07:18:30 +00:00
|
|
|
if (c.isspace ())
|
2011-02-22 01:34:19 +00:00
|
|
|
; /* Ignore whitespace */
|
2010-12-08 07:18:30 +00:00
|
|
|
else if (c == ';')
|
|
|
|
state = State.LINE_COMMENT;
|
|
|
|
else if (c == '{')
|
|
|
|
state = State.BRACE_COMMENT;
|
|
|
|
else if (c == '[')
|
|
|
|
state = State.TAG_START;
|
2011-02-22 01:34:19 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
offset--;
|
|
|
|
state = State.MOVE_TEXT;
|
|
|
|
continue;
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
2011-02-22 01:34:19 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case State.MOVE_TEXT:
|
|
|
|
home_state = State.MOVE_TEXT;
|
|
|
|
if (c.isspace ())
|
|
|
|
; /* Ignore whitespace */
|
|
|
|
else if (c == ';')
|
|
|
|
state = State.LINE_COMMENT;
|
|
|
|
else if (c == '{')
|
|
|
|
state = State.BRACE_COMMENT;
|
|
|
|
else if (c == '*')
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
if (rav_level == 0)
|
|
|
|
{
|
2011-01-01 03:24:41 +00:00
|
|
|
game.result = PGNGame.RESULT_IN_PROGRESS;
|
2010-12-08 07:18:30 +00:00
|
|
|
games.append (game);
|
|
|
|
game = new PGNGame ();
|
2011-02-22 01:34:19 +00:00
|
|
|
state = State.TAGS;
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
|
|
|
}
|
2011-02-22 01:34:19 +00:00
|
|
|
else if (c == '.')
|
|
|
|
{
|
|
|
|
offset--;
|
|
|
|
state = State.PERIOD;
|
|
|
|
}
|
|
|
|
else if (c.isalnum ())
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
token_start = offset;
|
|
|
|
state = State.SYMBOL;
|
|
|
|
}
|
2011-02-22 01:34:19 +00:00
|
|
|
else if (c == '$')
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
token_start = offset + 1;
|
|
|
|
state = State.NAG;
|
|
|
|
}
|
2011-02-22 01:34:19 +00:00
|
|
|
else if (c == '(')
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
rav_level++;
|
|
|
|
continue;
|
|
|
|
}
|
2011-02-22 01:34:19 +00:00
|
|
|
else if (c == ')')
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
if (rav_level == 0)
|
|
|
|
state = State.ERROR;
|
|
|
|
else
|
|
|
|
rav_level--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State.ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.LINE_COMMENT:
|
|
|
|
if (c == '\n')
|
2011-02-22 01:34:19 +00:00
|
|
|
state = home_state;
|
2010-12-08 07:18:30 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case State.BRACE_COMMENT:
|
|
|
|
if (c == '}')
|
2011-02-22 01:34:19 +00:00
|
|
|
state = home_state;
|
2010-12-08 07:18:30 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case State.TAG_START:
|
|
|
|
if (c.isspace ())
|
|
|
|
continue;
|
|
|
|
else if (c.isalnum())
|
|
|
|
{
|
|
|
|
token_start = offset;
|
|
|
|
state = State.TAG_NAME;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State.ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.TAG_NAME:
|
|
|
|
if (c.isspace ())
|
|
|
|
{
|
2011-02-22 01:34:19 +00:00
|
|
|
tag_name = data[(long)token_start:(long)offset];
|
2010-12-08 07:18:30 +00:00
|
|
|
state = State.PRE_TAG_VALUE;
|
|
|
|
}
|
|
|
|
else if (c.isalnum() || c == '_' || c == '+' || c == '#' || c == '=' || c == ':' || c == '-')
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
state = State.ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.PRE_TAG_VALUE:
|
|
|
|
if (c.isspace ())
|
|
|
|
continue;
|
|
|
|
else if (c == '"')
|
|
|
|
{
|
|
|
|
state = State.TAG_VALUE;
|
|
|
|
tag_value.erase ();
|
|
|
|
in_escape = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State.ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.TAG_VALUE:
|
|
|
|
if (c == '\\' && !in_escape)
|
|
|
|
in_escape = true;
|
|
|
|
else if (c == '"' && !in_escape)
|
|
|
|
state = State.POST_TAG_VALUE;
|
|
|
|
else if (c.isprint ())
|
|
|
|
{
|
|
|
|
tag_value.append_unichar (c);
|
|
|
|
in_escape = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State.ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.POST_TAG_VALUE:
|
|
|
|
if (c.isspace ())
|
|
|
|
continue;
|
|
|
|
else if (c == ']')
|
|
|
|
{
|
|
|
|
game.tags.insert (tag_name, tag_value.str);
|
2011-02-22 01:34:19 +00:00
|
|
|
state = State.TAGS;
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
state = State.ERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.SYMBOL:
|
|
|
|
/* NOTE: '/' not in spec but required for 1/2-1/2 symbol */
|
|
|
|
if (c.isalnum () || c == '_' || c == '+' || c == '#' || c == '=' || c == ':' || c == '-' || c == '/')
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
{
|
2011-02-22 01:34:19 +00:00
|
|
|
string symbol = data[(long)token_start:(long)offset];
|
2010-12-08 07:18:30 +00:00
|
|
|
|
|
|
|
bool is_number = true;
|
|
|
|
for (int i = 0; i < symbol.length; i++)
|
|
|
|
if (!symbol[i].isdigit ())
|
|
|
|
is_number = false;
|
|
|
|
|
2011-02-22 01:34:19 +00:00
|
|
|
state = State.MOVE_TEXT;
|
|
|
|
offset--;
|
|
|
|
|
2010-12-08 07:18:30 +00:00
|
|
|
/* Game termination markers */
|
2011-01-01 03:24:41 +00:00
|
|
|
if (symbol == PGNGame.RESULT_DRAW || symbol == PGNGame.RESULT_WHITE || symbol == PGNGame.RESULT_BLACK)
|
2010-12-08 07:18:30 +00:00
|
|
|
{
|
|
|
|
if (rav_level == 0)
|
|
|
|
{
|
|
|
|
game.result = symbol;
|
|
|
|
games.append (game);
|
|
|
|
game = new PGNGame ();
|
2011-02-22 01:34:19 +00:00
|
|
|
state = State.TAGS;
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!is_number)
|
|
|
|
{
|
|
|
|
if (rav_level == 0)
|
|
|
|
game.moves.append (symbol);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2011-02-22 01:34:19 +00:00
|
|
|
case State.PERIOD:
|
|
|
|
/* FIXME: Should check these move carefully, e.g. "1. e2" */
|
|
|
|
state = State.MOVE_TEXT;
|
|
|
|
break;
|
|
|
|
|
2010-12-08 07:18:30 +00:00
|
|
|
case State.NAG:
|
|
|
|
if (c.isdigit ())
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
{
|
2011-02-22 01:34:19 +00:00
|
|
|
//string nag = data[(long)token_start:(long)offset];
|
2010-12-08 07:18:30 +00:00
|
|
|
//stdout.printf ("nag = '%s'\n", nag);
|
2011-02-22 01:34:19 +00:00
|
|
|
state = State.MOVE_TEXT;
|
2010-12-08 07:18:30 +00:00
|
|
|
offset--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State.ERROR:
|
|
|
|
size_t char_offset = offset - line_offset - 1;
|
2011-02-22 01:34:19 +00:00
|
|
|
stderr.printf ("%d.%d: error: Unexpected character\n", line, (int) (char_offset + 1));
|
|
|
|
stderr.printf ("%s\n", data[(long)line_offset:(long)offset]);
|
2010-12-08 07:18:30 +00:00
|
|
|
for (int i = 0; i < char_offset; i++)
|
|
|
|
stderr.printf (" ");
|
|
|
|
stderr.printf ("^\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2011-01-01 02:42:03 +00:00
|
|
|
|
2011-02-22 01:34:19 +00:00
|
|
|
if (game.moves.length () > 0 || game.tags.size () > 0)
|
|
|
|
games.append (game);
|
|
|
|
|
2011-01-01 02:42:03 +00:00
|
|
|
/* Must have at least one game */
|
|
|
|
if (games == null)
|
|
|
|
throw new PGNError.LOAD_ERROR("No games in PGN file");
|
2011-07-01 10:58:45 +00:00
|
|
|
}
|
2011-02-22 01:34:19 +00:00
|
|
|
|
|
|
|
public PGN.from_file (File file) throws Error
|
|
|
|
{
|
2011-07-01 10:58:45 +00:00
|
|
|
uint8[] contents;
|
2012-02-03 16:00:37 +00:00
|
|
|
file.load_contents (null, out contents, null);
|
2011-07-01 10:58:45 +00:00
|
|
|
this.from_string ((string) contents);
|
2010-12-08 07:18:30 +00:00
|
|
|
}
|
2012-02-03 16:00:37 +00:00
|
|
|
}
|