Updated README: Equal sign not required with `--mode` flag.
[sgk-go] / sgf / sgfnode.c
CommitLineData
7eeb782e
AT
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
2 * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
3 * http://www.gnu.org/software/gnugo/ for more information. *
4 * *
5 * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
6 * 2008 and 2009 by the Free Software Foundation. *
7 * *
8 * This program is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public License as *
10 * published by the Free Software Foundation - version 3 or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License in file COPYING for more details. *
17 * *
18 * You should have received a copy of the GNU General Public *
19 * License along with this program; if not, write to the Free *
20 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
21 * Boston, MA 02111, USA. *
22\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
23
24/* Parts of this code were given to us by Tommy Thorn */
25
26#ifdef HAVE_CONFIG_H
27#include <config.h>
28#endif
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <ctype.h>
33#include <string.h>
34#include <assert.h>
35
36
37#if TIME_WITH_SYS_TIME
38# include <sys/time.h>
39# include <time.h>
40#else
41# if HAVE_SYS_TIME_H
42# include <sys/time.h>
43# else
44# include <time.h>
45# endif
46#endif
47
48#include "sgftree.h"
49#include "gg_utils.h"
50
51#define STRICT_SGF 's'
52#define LAX_SGF 'l'
53
54/* Set this to 1 if you want warnings for missing GM and FF properties. */
55#define VERBOSE_WARNINGS 0
56
57/* ================================================================ */
58/* Some utility functions. */
59/* ================================================================ */
60
61/*
62 * Utility: a checking, initializing malloc
63 */
64
65void *
66xalloc(unsigned int size)
67{
68 void *pt = malloc(size);
69
70 if (!pt) {
71 fprintf(stderr, "xalloc: Out of memory!\n");
72 exit(EXIT_FAILURE);
73 }
74
75 memset(pt, 0, (unsigned long) size);
76 return pt;
77}
78
79void *
80xrealloc(void *pt, unsigned int size)
81{
82 void *ptnew = realloc(pt, size);
83
84 if (!ptnew) {
85 fprintf(stderr, "xrealloc: Out of memory!\n");
86 exit(EXIT_FAILURE);
87 }
88 return ptnew;
89}
90
91
92/* ================================================================ */
93/* SGF Nodes */
94/* ================================================================ */
95
96
97/*
98 * Allocate memory for a new SGF node.
99 */
100
101SGFNode *
102sgfNewNode()
103{
104 SGFNode *newnode;
105 newnode = xalloc(sizeof(SGFNode));
106 newnode->next = NULL;
107 newnode->props = NULL;
108 newnode->parent = NULL;
109 newnode->child = NULL;
110 return newnode;
111}
112
113/*
114 * Recursively free an sgf node
115 */
116
117void
118sgfFreeNode(SGFNode *node)
119{
120 if (node == NULL)
121 return;
122 sgfFreeNode(node->next);
123 sgfFreeNode(node->child);
124 sgfFreeProperty(node->props);
125 free(node);
126}
127
128
129/*
130 * Add a generic text property to an SGF node.
131 */
132
133void
134sgfAddProperty(SGFNode *node, const char *name, const char *value)
135{
136 SGFProperty *prop = node->props;
137
138 if (prop)
139 while (prop->next)
140 prop = prop->next;
141
142 sgfMkProperty(name, value, node, prop);
143}
144
145
146/*
147 * Add an integer property to an SGF node.
148 */
149
150void
151sgfAddPropertyInt(SGFNode *node, const char *name, long val)
152{
153 char buffer[10];
154
155 gg_snprintf(buffer, 10, "%ld", val);
156 sgfAddProperty(node, name, buffer);
157}
158
159
160/*
161 * Add a float property to an SGF node.
162 */
163
164void
165sgfAddPropertyFloat(SGFNode *node, const char *name, float val)
166{
167 char buffer[10];
168
169 gg_snprintf(buffer, 10, "%3.1f", val);
170 sgfAddProperty(node, name, buffer);
171}
172
173
174/*
175 * Read a property as int from an SGF node.
176 */
177
178int
179sgfGetIntProperty(SGFNode *node, const char *name, int *value)
180{
181 SGFProperty *prop;
182 short nam = name[0] | name[1] << 8;
183
184 for (prop = node->props; prop; prop = prop->next)
185 if (prop->name == nam) {
186 *value = atoi(prop->value);
187 return 1;
188 }
189
190 return 0;
191}
192
193
194/*
195 * Read a property as float from an SGF node.
196 */
197
198int
199sgfGetFloatProperty(SGFNode *node, const char *name, float *value)
200{
201 SGFProperty *prop;
202 short nam = name[0] | name[1] << 8;
203
204 for (prop = node->props; prop; prop = prop->next)
205 if (prop->name == nam) {
206 *value = (float) atof(prop->value);
207 /* MS-C warns of loss of data (double to float) */
208 return 1;
209 }
210
211 return 0;
212}
213
214
215/*
216 * Read a property as text from an SGF node.
217 */
218
219int
220sgfGetCharProperty(SGFNode *node, const char *name, char **value)
221{
222 SGFProperty *prop;
223 short nam = name[0] | name[1] << 8;
224
225 for (prop = node->props; prop; prop = prop->next)
226 if (prop->name == nam) {
227 *value = prop->value;
228 return 1;
229 }
230
231 return 0;
232}
233
234
235/*
236 * Is there a property of this type in the node?
237 */
238
239static int
240sgfHasProperty(SGFNode *node, const char *name)
241{
242 SGFProperty *prop;
243 short nam = name[0] | name[1] << 8;
244
245 for (prop = node->props; prop; prop = prop->next)
246 if (prop->name == nam)
247 return 1;
248
249 return 0;
250}
251
252
253/*
254 * Overwrite a property from an SGF node with text or create a new
255 * one if it does not exist.
256 */
257
258void
259sgfOverwriteProperty(SGFNode *node, const char *name, const char *text)
260{
261 SGFProperty *prop;
262 short nam = name[0] | name[1] << 8;
263
264 for (prop = node->props; prop; prop = prop->next)
265 if (prop->name == nam) {
266 prop->value = xrealloc(prop->value, strlen(text)+1);
267 strcpy(prop->value, text);
268 return;
269 }
270
271 sgfAddProperty(node, name, text);
272}
273
274
275/*
276 * Overwrite an int property in an SGF node with val or create a new
277 * one if it does not exist.
278 */
279
280void
281sgfOverwritePropertyInt(SGFNode *node, const char *name, int val)
282{
283 SGFProperty *prop;
284 short nam = name[0] | name[1] << 8;
285
286 for (prop = node->props; prop; prop = prop->next)
287 if (prop->name == nam) {
288 prop->value = xrealloc(prop->value, 12);
289 gg_snprintf(prop->value, 12, "%d", val);
290 return;
291 }
292
293 sgfAddPropertyInt(node, name, val);
294}
295
296
297/*
298 * Overwrite a float property in the gametree with val or create
299 * a new one if it does not exist.
300 */
301
302void
303sgfOverwritePropertyFloat(SGFNode *node, const char *name, float val)
304{
305 SGFProperty *prop;
306 short nam = name[0] | name[1] << 8;
307
308 for (prop = node->props; prop; prop = prop->next)
309 if (prop->name == nam) {
310 prop->value = xrealloc(prop->value, 15);
311 gg_snprintf(prop->value, 15, "%3.1f", val);
312 return;
313 }
314
315 sgfAddPropertyFloat(node, name, val);
316}
317
318
319/*
320 * Goto previous node.
321 */
322
323SGFNode *
324sgfPrev(SGFNode *node)
325{
326 SGFNode *q;
327 SGFNode *prev;
328
329 if (!node->parent)
330 return NULL;
331
332 q = node->parent->child;
333 prev = NULL;
334 while (q && q != node) {
335 prev = q;
336 q = q->next;
337 }
338
339 return prev;
340}
341
342
343/*
344 * Goto root node.
345 */
346
347SGFNode *
348sgfRoot(SGFNode *node)
349{
350 while (node->parent)
351 node = node->parent;
352
353 return node;
354}
355
356
357/* ================================================================ */
358/* SGF Properties */
359/* ================================================================ */
360
361
362/*
363 * Make an SGF property.
364 */
365static SGFProperty *
366do_sgf_make_property(short sgf_name, const char *value,
367 SGFNode *node, SGFProperty *last)
368{
369 SGFProperty *prop;
370
371 prop = (SGFProperty *) xalloc(sizeof(SGFProperty));
372 prop->name = sgf_name;
373 prop->value = xalloc(strlen(value) + 1);
374 strcpy(prop->value, value);
375 prop->next = NULL;
376
377 if (last == NULL)
378 node->props = prop;
379 else
380 last->next = prop;
381
382 return prop;
383}
384
385
386/* Make an SGF property. In case of a property with a range it
387 * expands it and makes several properties instead.
388 */
389SGFProperty *
390sgfMkProperty(const char *name, const char *value,
391 SGFNode *node, SGFProperty *last)
392{
393 static const short properties_allowing_ranges[12] = {
394 /* Board setup properties. */
395 SGFAB, SGFAW, SGFAE,
396
397 /* Markup properties. */
398 SGFCR, SGFMA, SGFSQ, SGFTR, SGFDD, SGFSL,
399
400 /* Miscellaneous properties. */
401 SGFVW,
402
403 /* Go-specific properties. */
404 SGFTB, SGFTW
405 };
406
407 int k;
408 short sgf_name;
409
410 if (strlen(name) == 1)
411 sgf_name = name[0] | (short) (' ' << 8);
412 else
413 sgf_name = name[0] | name[1] << 8;
414
415 for (k = 0; k < 12; k++) {
416 if (properties_allowing_ranges[k] == sgf_name)
417 break;
418 }
419
420 if (k < 12
421 && strlen(value) == 5
422 && value[2] == ':') {
423 char x1 = value[0];
424 char y1 = value[1];
425 char x2 = value[3];
426 char y2 = value[4];
427 char new_value[] = "xy";
428
429 if (x1 <= x2 && y1 <= y2) {
430 for (new_value[0] = x1; new_value[0] <= x2; new_value[0]++) {
431 for (new_value[1] = y1; new_value[1] <= y2; new_value[1]++)
432 last = do_sgf_make_property(sgf_name, new_value, node, last);
433 }
434
435 return last;
436 }
437 }
438
439 /* Not a range property. */
440 return do_sgf_make_property(sgf_name, value, node, last);
441}
442
443
444/*
445 * Recursively free an SGF property.
446 *
447 */
448
449void
450sgfFreeProperty(SGFProperty *prop)
451{
452 if (prop == NULL)
453 return;
454 sgfFreeProperty(prop->next);
455 free(prop->value);
456 free(prop);
457}
458
459
460/* ================================================================ */
461/* High level functions */
462/* ================================================================ */
463
464
465/*
466 * Add a stone to the current or the given node.
467 * Return the node where the stone was added.
468 */
469
470SGFNode *
471sgfAddStone(SGFNode *node, int color, int movex, int movey)
472{
473 char move[3];
474
475 sprintf(move, "%c%c", movey + 'a', movex + 'a');
476 sgfAddProperty(node, (color == BLACK) ? "AB" : "AW", move);
477
478 return node;
479}
480
481
482/*
483 * Add a move to the gametree.
484 */
485
486SGFNode *
487sgfAddPlay(SGFNode *node, int who, int movex, int movey)
488{
489 char move[3];
490 SGFNode *new;
491
492 /* a pass move? */
493 if (movex == -1 && movey == -1)
494 move[0] = 0;
495 else
496 sprintf(move, "%c%c", movey + 'a', movex + 'a');
497
498 if (node->child)
499 new = sgfStartVariantFirst(node->child);
500 else {
501 new = sgfNewNode();
502 node->child = new;
503 new->parent = node;
504 }
505
506 sgfAddProperty(new, (who == BLACK) ? "B" : "W", move);
507
508 return new;
509}
510
511
512/*
513 * Add a move to the gametree. New variations are added after the old
514 * ones rather than before.
515 */
516
517SGFNode *
518sgfAddPlayLast(SGFNode *node, int who, int movex, int movey)
519{
520 char move[3];
521 SGFNode *new;
522
523 /* a pass move? */
524 if (movex == -1 && movey == -1)
525 move[0] = 0;
526 else
527 sprintf(move, "%c%c", movey + 'a', movex + 'a');
528
529 new = sgfAddChild(node);
530 sgfAddProperty(new, (who == BLACK) ? "B" : "W", move);
531
532 return new;
533}
534
535
536SGFNode *
537sgfCreateHeaderNode(int boardsize, float komi, int handicap)
538{
539 SGFNode *root = sgfNewNode();
540
541 sgfAddPropertyInt(root, "SZ", boardsize);
542 sgfAddPropertyFloat(root, "KM", komi);
543 sgfAddPropertyInt(root, "HA", handicap);
544
545 return root;
546}
547
548
549/*
550 * Add a comment to an SGF node.
551 */
552
553SGFNode *
554sgfAddComment(SGFNode *node, const char *comment)
555{
556 sgfAddProperty(node, "C ", comment);
557
558 return node;
559}
560
561
562/*
563 * Place text on the board at position (i, j).
564 */
565
566SGFNode *
567sgfBoardText(SGFNode *node, int i, int j, const char *text)
568{
569 void *str = xalloc(strlen(text) + 3);
570
571 sprintf(str, "%c%c:%s", j+'a', i+'a', text);
572 sgfAddProperty(node, "LB", str);
573 free(str);
574
575 return node;
576}
577
578
579/*
580 * Place a character on the board at position (i, j).
581 */
582
583SGFNode *
584sgfBoardChar(SGFNode *node, int i, int j, char c)
585{
586 char text[2] = "";
587
588 text[0] = c;
589 text[1] = 0;
590
591 return sgfBoardText(node, i, j, text);
592}
593
594
595/*
596 * Place a number on the board at position (i, j).
597 */
598
599SGFNode *
600sgfBoardNumber(SGFNode *node, int i, int j, int number)
601{
602 char text[10];
603
604 gg_snprintf(text, 10, "%c%c:%i", j+'a', i+'a', number);
605 sgfAddProperty(node, "LB", text);
606
607 return node;
608}
609
610
611/*
612 * Place a triangle mark on the board at position (i, j).
613 */
614
615SGFNode *
616sgfTriangle(SGFNode *node, int i, int j)
617{
618 char text[3];
619
620 gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
621 sgfAddProperty(node, "TR", text);
622
623 return node;
624}
625
626
627/*
628 * Place a label on the board at position (i, j).
629 */
630
631SGFNode *
632sgfLabel(SGFNode *node, const char *label, int i, int j)
633{
634 /* allows 12 chars labels - more than enough */
635 char text[16];
636
637 gg_snprintf(text, 16, "%c%c:%s", j+'a', i+'a', label);
638 sgfAddProperty(node, "LB", text);
639
640 return node;
641}
642
643
644/*
645 * Place a numeric label on the board at position (i, j).
646 */
647
648SGFNode *
649sgfLabelInt(SGFNode *node, int num, int i, int j)
650{
651 char text[16];
652
653 gg_snprintf(text, 16, "%c%c:%d", j+'a', i+'a', num);
654 sgfAddProperty(node, "LB", text);
655
656 return node;
657}
658
659
660/*
661 * Place a circle mark on the board at position (i, j).
662 */
663
664SGFNode *
665sgfCircle(SGFNode *node, int i, int j)
666{
667 char text[3];
668
669 gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
670 sgfAddProperty(node, "CR", text);
671
672 return node;
673}
674
675
676/*
677 * Place a square mark on the board at position (i, j).
678 */
679
680SGFNode *
681sgfSquare(SGFNode *node, int i, int j)
682{
683 return sgfMark(node, i, j); /* cgoban 1.9.5 does not understand SQ */
684}
685
686
687/*
688 * Place a (square) mark on the board at position (i, j).
689 */
690
691SGFNode *
692sgfMark(SGFNode *node, int i, int j)
693{
694 char text[3];
695
696 gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
697 sgfAddProperty(node, "MA", text);
698
699 return node;
700}
701
702
703/*
704 * Start a new variant. Returns a pointer to the new node.
705 */
706
707SGFNode *
708sgfStartVariant(SGFNode *node)
709{
710 assert(node);
711 assert(node->parent);
712
713 while (node->next)
714 node = node->next;
715 node->next = sgfNewNode();
716 node->next->parent = node->parent;
717
718 return node->next;
719}
720
721
722/*
723 * Start a new variant as first child. Returns a pointer to the new node.
724 */
725
726SGFNode *
727sgfStartVariantFirst(SGFNode *node)
728{
729 SGFNode *old_first_child = node;
730 SGFNode *new_first_child = sgfNewNode();
731
732 assert(node);
733 assert(node->parent);
734
735 new_first_child->next = old_first_child;
736 new_first_child->parent = old_first_child->parent;
737
738 new_first_child->parent->child = new_first_child;
739
740 return new_first_child;
741}
742
743
744/*
745 * If no child exists, add one. Otherwise add a sibling to the
746 * existing children. Returns a pointer to the new node.
747 */
748
749SGFNode *
750sgfAddChild(SGFNode *node)
751{
752 SGFNode *new_node = sgfNewNode();
753 assert(node);
754
755 new_node->parent = node;
756
757 if (!node->child)
758 node->child = new_node;
759 else {
760 node = node->child;
761 while (node->next)
762 node = node->next;
763 node->next = new_node;
764 }
765
766 return new_node;
767}
768
769
770/*
771 * Write result of the game to the game tree.
772 */
773
774void
775sgfWriteResult(SGFNode *node, float score, int overwrite)
776{
777 char text[8];
778 char winner;
779 float s;
780 int dummy;
781
782 /* If not writing to the SGF file, skip everything and return now. */
783 if (!node)
784 return;
785
786 /* If not overwriting and there already is a result property, return. */
787 if (!overwrite)
788 if (sgfGetIntProperty(node, "RE", &dummy))
789 return;
790
791 if (score > 0.0) {
792 winner = 'W';
793 s = score;
794 }
795 else if (score < 0.0) {
796 winner = 'B';
797 s = -score;
798 }
799 else {
800 winner = '0';
801 s = 0;
802 }
803
804 if (winner == '0')
805 gg_snprintf(text, 8, "0");
806 else if (score < 1000.0 && score > -1000.0)
807 gg_snprintf(text, 8, "%c+%3.1f", winner, s);
808 else
809 gg_snprintf(text, 8, "%c+%c", winner, 'R');
810 sgfOverwriteProperty(node, "RE", text);
811}
812
813
814static void
815sgf_write_header_reduced(SGFNode *root, int overwrite)
816{
817 time_t curtime = time(NULL);
818 struct tm *loctime = localtime(&curtime);
819 char str[128];
820 int dummy;
821
822 gg_snprintf(str, 128, "%4.4i-%2.2i-%2.2i",
823 loctime->tm_year+1900, loctime->tm_mon+1, loctime->tm_mday);
824 if (overwrite || !sgfGetIntProperty(root, "DT", &dummy))
825 sgfOverwriteProperty(root, "DT", str);
826 if (overwrite || !sgfGetIntProperty(root, "AP", &dummy))
827 sgfOverwriteProperty(root, "AP", "GNU Go:"VERSION);
828 sgfOverwriteProperty(root, "FF", "4");
829}
830
831
832void
833sgf_write_header(SGFNode *root, int overwrite, int seed, float komi,
834 int handicap, int level, int rules)
835{
836 char str[128];
837 int dummy;
838
839 gg_snprintf(str, 128, "GNU Go %s Random Seed %d level %d",
840 VERSION, seed, level);
841 if (overwrite || !sgfGetIntProperty(root, "GN", &dummy))
842 sgfOverwriteProperty(root, "GN", str);
843 if (overwrite || !sgfGetIntProperty(root, "RU", &dummy))
844 sgfOverwriteProperty(root, "RU", rules ? "Chinese" : "Japanese");
845 sgfOverwritePropertyFloat(root, "KM", komi);
846 sgfOverwritePropertyInt(root, "HA", handicap);
847
848 sgf_write_header_reduced(root, overwrite);
849}
850
851
852/* ================================================================ */
853/* Read SGF tree */
854/* ================================================================ */
855
856
857#define MAX_FILE_BUFFER 200000 /* buffer for reading SGF file. */
858
859/*
860 * SGF grammar:
861 *
862 * Collection = GameTree { GameTree }
863 * GameTree = "(" Sequence { GameTree } ")"
864 * Sequence = Node { Node }
865 * Node = ";" { Property }
866 * Property = PropIdent PropValue { PropValue }
867 * PropIdent = UcLetter { UcLetter }
868 * PropValue = "[" CValueType "]"
869 * CValueType = (ValueType | Compose)
870 * ValueType = (None | Number | Real | Double | Color | SimpleText |
871 * Text | Point | Move | Stone)
872 *
873 * The above grammar has a number of simple properties which enables us
874 * to write a simpler parser:
875 * 1) There is never a need for backtracking
876 * 2) The only recursion is on gametree.
877 * 3) Tokens are only one character
878 *
879 * We will use a global state to keep track of the remaining input
880 * and a global char variable, `lookahead' to hold the next token.
881 * The function `nexttoken' skips whitespace and fills lookahead with
882 * the new token.
883 */
884
885
886static void parse_error(const char *msg, int arg);
887static void nexttoken(void);
888static void match(int expected);
889
890
891static FILE *sgffile;
892
893
894#define sgf_getch() (getc(sgffile))
895
896
897static char *sgferr;
898#ifdef TEST_SGFPARSER
899static int sgferrarg;
900#endif
901static int sgferrpos;
902
903static int lookahead;
904
905
906/* ---------------------------------------------------------------- */
907/* Parsing primitives */
908/* ---------------------------------------------------------------- */
909
910
911static void
912parse_error(const char *msg, int arg)
913{
914 fprintf(stderr, msg, arg);
915 fprintf(stderr, "\n");
916 exit(EXIT_FAILURE);
917}
918
919
920static void
921nexttoken()
922{
923 do
924 lookahead = sgf_getch();
925 while (isspace(lookahead));
926}
927
928
929static void
930match(int expected)
931{
932 if (lookahead != expected)
933 parse_error("expected: %c", expected);
934 else
935 nexttoken();
936}
937
938/* ---------------------------------------------------------------- */
939/* The parser proper */
940/* ---------------------------------------------------------------- */
941
942
943static void
944propident(char *buffer, int size)
945{
946 if (lookahead == EOF || !isupper(lookahead))
947 parse_error("Expected an upper case letter.", 0);
948
949 while (lookahead != EOF && isalpha(lookahead)) {
950 if (isupper(lookahead) && size > 1) {
951 *buffer++ = lookahead;
952 size--;
953 }
954 nexttoken();
955 }
956 *buffer = '\0';
957}
958
959
960static void
961propvalue(char *buffer, int size)
962{
963 char *p = buffer;
964
965 match('[');
966 while (lookahead != ']' && lookahead != EOF) {
967 if (lookahead == '\\') {
968 lookahead = sgf_getch();
969 /* Follow the FF4 definition of backslash */
970 if (lookahead == '\r') {
971 lookahead = sgf_getch();
972 if (lookahead == '\n')
973 lookahead = sgf_getch();
974 }
975 else if (lookahead == '\n') {
976 lookahead = sgf_getch();
977 if (lookahead == '\r')
978 lookahead = sgf_getch();
979 }
980 }
981 if (size > 1) {
982 *p++ = lookahead;
983 size--;
984 }
985 lookahead = sgf_getch();
986 }
987 match(']');
988
989 /* Remove trailing whitespace. The double cast below is needed
990 * because "char" may be represented as a signed char, in which case
991 * characters between 128 and 255 would be negative and a direct
992 * cast to int would cause a negative value to be passed to isspace,
993 * possibly causing an assertion failure.
994 */
995 --p;
996 while (p > buffer && isspace((int) (unsigned char) *p))
997 --p;
998 *++p = '\0';
999}
1000
1001
1002static SGFProperty *
1003property(SGFNode *n, SGFProperty *last)
1004{
1005 char name[3];
1006 char buffer[4000];
1007
1008 propident(name, sizeof(name));
1009 do {
1010 propvalue(buffer, sizeof(buffer));
1011 last = sgfMkProperty(name, buffer, n, last);
1012 } while (lookahead == '[');
1013 return last;
1014}
1015
1016
1017static void
1018node(SGFNode *n)
1019{
1020 SGFProperty *last = NULL;
1021 match(';');
1022 while (lookahead != EOF && isupper(lookahead))
1023 last = property(n, last);
1024}
1025
1026
1027static SGFNode *
1028sequence(SGFNode *n)
1029{
1030 node(n);
1031 while (lookahead == ';') {
1032 SGFNode *new = sgfNewNode();
1033 new->parent = n;
1034 n->child = new;
1035 n = new;
1036 node(n);
1037 }
1038 return n;
1039}
1040
1041
1042static void
1043gametree(SGFNode **p, SGFNode *parent, int mode)
1044{
1045 if (mode == STRICT_SGF)
1046 match('(');
1047 else
1048 for (;;) {
1049 if (lookahead == EOF) {
1050 parse_error("Empty file?", 0);
1051 break;
1052 }
1053 if (lookahead == '(') {
1054 while (lookahead == '(')
1055 nexttoken();
1056 if (lookahead == ';')
1057 break;
1058 }
1059 nexttoken();
1060 }
1061
1062 /* The head is parsed */
1063 {
1064 SGFNode *head = sgfNewNode();
1065 SGFNode *last;
1066
1067 head->parent = parent;
1068 *p = head;
1069
1070 last = sequence(head);
1071 p = &last->child;
1072 while (lookahead == '(') {
1073 gametree(p, last, STRICT_SGF);
1074 p = &((*p)->next);
1075 }
1076 if (mode == STRICT_SGF)
1077 match(')');
1078 }
1079}
1080
1081
1082/*
1083 * Fuseki readers
1084 * Reads an SGF file for extract_fuseki in a compact way
1085 */
1086
1087static void
1088gametreefuseki(SGFNode **p, SGFNode *parent, int mode,
1089 int moves_per_game, int i)
1090{
1091 if (mode == STRICT_SGF)
1092 match('(');
1093 else
1094 for (;;) {
1095 if (lookahead == EOF) {
1096 parse_error("Empty file?", 0);
1097 break;
1098 }
1099 if (lookahead == '(') {
1100 while (lookahead == '(')
1101 nexttoken();
1102 if (lookahead == ';')
1103 break;
1104 }
1105 nexttoken();
1106 }
1107
1108 /* The head is parsed */
1109 {
1110
1111 SGFNode *head = sgfNewNode();
1112 SGFNode *last;
1113 head->parent = parent;
1114 *p = head;
1115
1116 last = sequence(head);
1117 p = &last->child;
1118 while (lookahead == '(') {
1119 if (last->props
1120 && (last->props->name == SGFB || last->props->name == SGFW))
1121 i++;
1122 /* break after number_of_moves moves in SGF file */
1123 if (i >= moves_per_game) {
1124 last->child = NULL;
1125 last->next = NULL;
1126 break;
1127 }
1128 else {
1129 gametreefuseki(p, last, mode, moves_per_game, i);
1130 p = &((*p)->next);
1131 }
1132 }
1133 if (mode == STRICT_SGF)
1134 match(')');
1135 }
1136}
1137
1138SGFNode *
1139readsgffilefuseki(const char *filename, int moves_per_game)
1140{
1141 SGFNode *root;
1142 int tmpi = 0;
1143
1144 if (strcmp(filename, "-") == 0)
1145 sgffile = stdin;
1146 else
1147 sgffile = fopen(filename, "r");
1148
1149 if (!sgffile)
1150 return NULL;
1151
1152
1153 nexttoken();
1154 gametreefuseki(&root, NULL, LAX_SGF, moves_per_game, 0);
1155
1156 fclose(sgffile);
1157
1158 if (sgferr) {
1159 fprintf(stderr, "Parse error: %s at position %d\n", sgferr, sgferrpos);
1160 sgfFreeNode(root);
1161 return NULL;
1162 }
1163
1164 /* perform some simple checks on the file */
1165 if (!sgfGetIntProperty(root, "GM", &tmpi)) {
1166 if (VERBOSE_WARNINGS)
1167 fprintf(stderr, "Couldn't find the game type (GM) attribute!\n");
1168 }
1169 else if (tmpi != 1) {
1170 fprintf(stderr, "SGF file might be for game other than go: %d\n", tmpi);
1171 fprintf(stderr, "Trying to load anyway.\n");
1172 }
1173
1174 if (!sgfGetIntProperty(root, "FF", &tmpi)) {
1175 if (VERBOSE_WARNINGS)
1176 fprintf(stderr, "Can not determine SGF spec version (FF)!\n");
1177 }
1178 else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS)
1179 fprintf(stderr, "Unsupported SGF spec version: %d\n", tmpi);
1180
1181 return root;
1182}
1183
1184
1185
1186
1187
1188/*
1189 * Wrapper around readsgf which reads from a file rather than a string.
1190 * Returns NULL if file will not open, or some other parsing error.
1191 * Filename "-" means read from stdin, and leave it open when done.
1192 */
1193
1194SGFNode *
1195readsgffile(const char *filename)
1196{
1197 SGFNode *root;
1198 int tmpi = 0;
1199
1200 if (strcmp(filename, "-") == 0)
1201 sgffile = stdin;
1202 else
1203 sgffile = fopen(filename, "r");
1204
1205 if (!sgffile)
1206 return NULL;
1207
1208
1209 nexttoken();
1210 gametree(&root, NULL, LAX_SGF);
1211
1212 if (sgffile != stdin)
1213 fclose(sgffile);
1214
1215 if (sgferr) {
1216 fprintf(stderr, "Parse error: %s at position %d\n", sgferr, sgferrpos);
1217 sgfFreeNode(root);
1218 return NULL;
1219 }
1220
1221 /* perform some simple checks on the file */
1222 if (!sgfGetIntProperty(root, "GM", &tmpi)) {
1223 if (VERBOSE_WARNINGS)
1224 fprintf(stderr, "Couldn't find the game type (GM) attribute!\n");
1225 }
1226 else if (tmpi != 1) {
1227 fprintf(stderr, "SGF file might be for game other than go: %d\n", tmpi);
1228 fprintf(stderr, "Trying to load anyway.\n");
1229 }
1230
1231 if (!sgfGetIntProperty(root, "FF", &tmpi)) {
1232 if (VERBOSE_WARNINGS)
1233 fprintf(stderr, "Can not determine SGF spec version (FF)!\n");
1234 }
1235 else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS)
1236 fprintf(stderr, "Unsupported SGF spec version: %d\n", tmpi);
1237
1238 return root;
1239}
1240
1241
1242
1243/* ================================================================ */
1244/* Write SGF tree */
1245/* ================================================================ */
1246
1247
1248#define OPTION_STRICT_FF4 0
1249
1250static int sgf_column = 0;
1251
1252static void
1253sgf_putc(int c, FILE *file)
1254{
1255 if (c == '\n' && sgf_column == 0)
1256 return;
1257
1258 fputc(c, file);
1259
1260 if (c == '\n')
1261 sgf_column = 0;
1262 else
1263 sgf_column++;
1264
1265 if (c == ']' && sgf_column > 60) {
1266 fputc('\n', file);
1267 sgf_column = 0;
1268 }
1269}
1270
1271static void
1272sgf_puts(const char *s, FILE *file)
1273{
1274 for (; *s; s++) {
1275 if (*s == '[' || *s == ']' || *s == '\\') {
1276 fputc('\\', file);
1277 sgf_column++;
1278 }
1279 fputc((int) *s, file);
1280 sgf_column++;
1281 }
1282}
1283
1284/* Print all properties with the given name in a node to file and mark
1285 * them as printed.
1286 *
1287 * If is_comment is 1, multiple properties are concatenated with a
1288 * newline. I.e. we write
1289 *
1290 * C[comment1
1291 * comment2]
1292 *
1293 * instead of
1294 *
1295 * C[comment1][comment2]
1296 *
1297 * Most other property types should be written in the latter style.
1298 */
1299
1300static void
1301sgf_print_name(FILE *file, short name)
1302{
1303 sgf_putc(name & 0xff, file);
1304 if (name >> 8 != ' ')
1305 sgf_putc(name >> 8, file);
1306}
1307
1308static void
1309sgf_print_property(FILE *file, SGFNode *node, short name, int is_comment)
1310{
1311 int n = 0;
1312 SGFProperty *prop;
1313
1314 for (prop = node->props; prop; prop = prop->next) {
1315 if (prop->name == name) {
1316 prop->name |= 0x20; /* Indicate already printed. */
1317 if (n == 0) {
1318 sgf_print_name(file, name);
1319 sgf_putc('[', file);
1320 }
1321 else if (is_comment)
1322 sgf_putc('\n', file);
1323 else {
1324 sgf_putc(']', file);
1325 sgf_putc('[', file);
1326 }
1327
1328 sgf_puts(prop->value, file);
1329 n++;
1330 }
1331 }
1332
1333 if (n > 0)
1334 sgf_putc(']', file);
1335
1336 /* Add a newline after certain properties. */
1337 if (name == SGFAB || name == SGFAW || name == SGFAE || (is_comment && n > 1))
1338 sgf_putc('\n', file);
1339}
1340
1341/*
1342 * Print all remaining unprinted property values at node N to file.
1343 */
1344
1345static void
1346sgfPrintRemainingProperties(FILE *file, SGFNode *node)
1347{
1348 SGFProperty *prop;
1349
1350 for (prop = node->props; prop; prop = prop->next)
1351 if (!(prop->name & 0x20))
1352 sgf_print_property(file, node, prop->name, 0);
1353}
1354
1355
1356/*
1357 * Print the property values of NAME at node N and mark it as printed.
1358 */
1359
1360static void
1361sgfPrintCharProperty(FILE *file, SGFNode *node, const char *name)
1362{
1363 short nam = name[0] | name[1] << 8;
1364
1365 sgf_print_property(file, node, nam, 0);
1366}
1367
1368
1369/*
1370 * Print comments from Node node.
1371 *
1372 * NOTE: cgoban does not print "C[comment1][comment2]" and I don't know
1373 * what the sgfspec says.
1374 */
1375
1376static void
1377sgfPrintCommentProperty(FILE *file, SGFNode *node, const char *name)
1378{
1379 short nam = name[0] | name[1] << 8;
1380
1381 sgf_print_property(file, node, nam, 1);
1382}
1383
1384
1385static void
1386unparse_node(FILE *file, SGFNode *node)
1387{
1388 sgf_putc(';', file);
1389 sgfPrintCharProperty(file, node, "B ");
1390 sgfPrintCharProperty(file, node, "W ");
1391 sgfPrintCommentProperty(file, node, "N ");
1392 sgfPrintCommentProperty(file, node, "C ");
1393 sgfPrintRemainingProperties(file, node);
1394}
1395
1396
1397static void
1398unparse_root(FILE *file, SGFNode *node)
1399{
1400 sgf_putc(';', file);
1401
1402 if (sgfHasProperty(node, "GM"))
1403 sgfPrintCharProperty(file, node, "GM");
1404 else {
1405 fputs("GM[1]", file);
1406 sgf_column += 5;
1407 }
1408
1409 sgfPrintCharProperty(file, node, "FF");
1410 sgf_putc('\n', file);
1411
1412 sgfPrintCharProperty(file, node, "SZ");
1413 sgf_putc('\n', file);
1414
1415 sgfPrintCharProperty(file, node, "GN");
1416 sgf_putc('\n', file);
1417
1418 sgfPrintCharProperty(file, node, "DT");
1419 sgf_putc('\n', file);
1420
1421 sgfPrintCommentProperty(file, node, "PB");
1422 sgfPrintCommentProperty(file, node, "BR");
1423 sgf_putc('\n', file);
1424
1425 sgfPrintCommentProperty(file, node, "PW");
1426 sgfPrintCommentProperty(file, node, "WR");
1427 sgf_putc('\n', file);
1428
1429 sgfPrintCommentProperty(file, node, "N ");
1430 sgfPrintCommentProperty(file, node, "C ");
1431 sgfPrintRemainingProperties(file, node);
1432
1433 sgf_putc('\n', file);
1434}
1435
1436
1437/*
1438 * p->child is the next move.
1439 * p->next is the next variation
1440 */
1441
1442static void
1443unparse_game(FILE *file, SGFNode *node, int root)
1444{
1445 if (!root)
1446 sgf_putc('\n', file);
1447 sgf_putc('(', file);
1448 if (root)
1449 unparse_root(file, node);
1450 else
1451 unparse_node(file, node);
1452
1453 node = node->child;
1454 while (node != NULL && node->next == NULL) {
1455 unparse_node(file, node);
1456 node = node->child;
1457 }
1458
1459 while (node != NULL) {
1460 unparse_game(file, node, 0);
1461 node = node->next;
1462 }
1463 sgf_putc(')', file);
1464 if (root)
1465 sgf_putc('\n', file);
1466}
1467
1468/* Printed properties are marked by adding the 0x20 bit to the
1469 * property name (changing an upper case letter to lower case). This
1470 * function removes this mark so that we can print the property next
1471 * time too. It recurses to all properties in the linked list.
1472 */
1473static void
1474restore_property(SGFProperty *prop)
1475{
1476 if (prop) {
1477 restore_property(prop->next);
1478 prop->name &= ~0x20;
1479 }
1480}
1481
1482/* When called with the tree root, recurses to all properties in the
1483 * tree and removes all print marks.
1484 */
1485static void
1486restore_node(SGFNode *node)
1487{
1488 if (node) {
1489 restore_property(node->props);
1490 restore_node(node->child);
1491 restore_node(node->next);
1492 }
1493}
1494
1495
1496/*
1497 * Opens filename and writes the game stored in the sgf structure.
1498 */
1499
1500int
1501writesgf(SGFNode *root, const char *filename)
1502{
1503 FILE *outfile;
1504
1505 if (strcmp(filename, "-") == 0)
1506 outfile = stdout;
1507 else
1508 outfile = fopen(filename, "w");
1509
1510 if (!outfile) {
1511 fprintf(stderr, "Can not open %s\n", filename);
1512 return 0;
1513 }
1514
1515 sgf_write_header_reduced(root, 0);
1516
1517 sgf_column = 0;
1518 unparse_game(outfile, root, 1);
1519 if (outfile != stdout)
1520 fclose(outfile);
1521
1522 /* Remove "printed" marks so that the tree can be written multiple
1523 * times.
1524 */
1525 restore_node(root);
1526
1527 return 1;
1528}
1529
1530
1531#ifdef TEST_SGFPARSER
1532int
1533main()
1534{
1535 static char buffer[25000];
1536 static char output[25000];
1537 SGFNode *game;
1538
1539 sgffile = stdin;
1540
1541 nexttoken();
1542 gametree(&game, LAX_SGF);
1543 if (sgferr) {
1544 fprintf(stderr, "Parse error:");
1545 fprintf(stderr, sgferr, sgferrarg);
1546 fprintf(stderr, " at position %d\n", sgferrpos);
1547 }
1548 else {
1549 unparse_game(stdin, game, 1);
1550 write(1, output, outputp - output);
1551 }
1552}
1553#endif
1554
1555
1556
1557/*
1558 * Local Variables:
1559 * tab-width: 8
1560 * c-basic-offset: 2
1561 * End:
1562 */