1 //
2 // This file is part of Gambit
3 // Copyright (c) 1994-2016, The Gambit Project (http://www.gambit-project.org)
4 //
5 // FILE: src/libgambit/file.cc
6 // Parser for reading game savefiles
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, 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 for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 //
22
23 #include <cstdlib>
24 #include <cctype>
25 #include <iostream>
26 #include <sstream>
27 #include <map>
28
29 #include "gambit/gambit.h"
30 // for explicit access to turning off canonicalization
31 #include "gambit/gametree.h"
32
33
34 namespace {
35 // This anonymous namespace encapsulates the file-parsing code
36
37 using namespace Gambit;
38
39 typedef enum {
40 TOKEN_NUMBER = 0, TOKEN_TEXT = 1, TOKEN_SYMBOL = 2,
41 TOKEN_LBRACE = 3, TOKEN_RBRACE = 4, TOKEN_COMMA = 5, TOKEN_EOF = 6
42 } GameFileToken;
43
44 //!
45 //! This parser class implements the semantics of Gambit savefiles,
46 //! including the nonsignificance of whitespace and the possibility of
47 //! escaped-quotes within text labels.
48 //!
49 class GameParserState {
50 private:
51 std::istream &m_file;
52
53 int m_currentLine;
54 int m_currentColumn;
55 GameFileToken m_lastToken;
56 std::string m_lastText;
57
58 void ReadChar(char& c);
59 void UnreadChar(void);
60 void IncreaseLine(void);
61
62 public:
GameParserState(std::istream & p_file)63 GameParserState(std::istream &p_file) :
64 m_file(p_file), m_currentLine(1), m_currentColumn(1) { }
65
66 GameFileToken GetNextToken(void);
GetCurrentToken(void) const67 GameFileToken GetCurrentToken(void) const { return m_lastToken; }
GetCurrentLine(void) const68 int GetCurrentLine(void) const { return m_currentLine; }
GetCurrentColumn(void) const69 int GetCurrentColumn(void) const { return m_currentColumn; }
70 std::string CreateLineMsg(const std::string &msg);
GetLastText(void) const71 const std::string &GetLastText(void) const { return m_lastText; }
72 };
73
ReadChar(char & c)74 void GameParserState::ReadChar(char& c)
75 {
76 m_file.get(c);
77 m_currentColumn++;
78 }
79
UnreadChar(void)80 void GameParserState::UnreadChar(void)
81 {
82 m_file.unget();
83 m_currentColumn--;
84 }
85
IncreaseLine(void)86 void GameParserState::IncreaseLine(void){
87 m_currentLine++;
88 // Reset column
89 m_currentColumn = 1;
90 }
91
GetNextToken(void)92 GameFileToken GameParserState::GetNextToken(void)
93 {
94 char c = ' ';
95 if (m_file.eof()) {
96 return (m_lastToken = TOKEN_EOF);
97 }
98
99 while (isspace(c)) {
100 ReadChar(c);
101 if (!m_file.good()) {
102 return (m_lastToken = TOKEN_EOF);
103 }
104 else if (c == '\n') {
105 IncreaseLine();
106 }
107 }
108
109 if (c == '{') {
110 return (m_lastToken = TOKEN_LBRACE);
111 }
112 else if (c == '}') {
113 return (m_lastToken = TOKEN_RBRACE);
114 }
115 else if (c == ',') {
116 return (m_lastToken = TOKEN_COMMA);
117 }
118 else if (isdigit(c) || c == '-' || c == '+') {
119 std::string buf;
120 buf += c;
121 ReadChar(c);
122
123 while (!m_file.eof() && m_file.good() && isdigit(c)) {
124 buf += c;
125 ReadChar(c);
126 }
127
128 if (m_file.eof() || !m_file.good()) {
129 m_lastText = buf;
130 return (m_lastToken = TOKEN_NUMBER);
131 }
132
133 if (c == '.') {
134 buf += c;
135 ReadChar(c);
136 while (isdigit(c)) {
137 buf += c;
138 ReadChar(c);
139 }
140
141 if (c == 'e' || c == 'E') {
142 buf += c;
143 ReadChar(c);
144 if (c == '+' && c == '-' && !isdigit(c)) {
145 throw InvalidFileException(CreateLineMsg("Invalid Token +/-"));
146 }
147 buf += c;
148 ReadChar(c);
149 while (isdigit(c)) {
150 buf += c;
151 ReadChar(c);
152 }
153 }
154
155 UnreadChar();
156 m_lastText = buf;
157
158 return (m_lastToken = TOKEN_NUMBER);
159 }
160 else if (c == '/') {
161 buf += c;
162 ReadChar(c);
163 while (isdigit(c)) {
164 buf += c;
165 ReadChar(c);
166 }
167 UnreadChar();
168 m_lastText = buf;
169 return (m_lastToken = TOKEN_NUMBER);
170 }
171 else if (c == 'e' || c == 'E') {
172 buf += c;
173 ReadChar(c);
174 if (c == '+' && c == '-' && !isdigit(c)) {
175 throw InvalidFileException(CreateLineMsg("Invalid Token +/-"));
176 }
177 buf += c;
178 ReadChar(c);
179 while (isdigit(c)) {
180 buf += c;
181 ReadChar(c);
182 }
183 UnreadChar();
184 m_lastText = buf;
185 return (m_lastToken = TOKEN_NUMBER);
186 }
187 else {
188 UnreadChar();
189 m_lastText = buf;
190 return (m_lastToken = TOKEN_NUMBER);
191 }
192 }
193 else if (c == '.') {
194 std::string buf;
195 buf += c;
196 ReadChar(c);
197
198 while (isdigit(c)) {
199 buf += c;
200 ReadChar(c);
201 }
202 UnreadChar();
203 m_lastText = buf;
204 return (m_lastToken = TOKEN_NUMBER);
205 }
206
207 else if (c == '"') {
208 // We need to do a little magic here, since escaped quotes inside
209 // the string are treated as quotes (not end-of-string)
210 UnreadChar();
211 char a;
212
213 m_lastText = "";
214
215 do {
216 ReadChar(a);
217 if (a == '\n') {
218 IncreaseLine();
219 }
220 } while (isspace(a));
221
222 if (a == '\"') {
223 bool lastslash = false;
224
225 ReadChar(a);
226 while (a != '\"' || lastslash) {
227 if (m_file.eof() || !m_file.good()) {
228 throw InvalidFileException(CreateLineMsg("End of file encountered when reading string label"));
229 }
230 if (lastslash && a == '"') {
231 m_lastText += '"';
232 }
233 else if (lastslash) {
234 m_lastText += '\\';
235 m_lastText += a;
236 }
237 else if (a != '\\')
238 m_lastText += a;
239
240 lastslash = (a == '\\');
241 ReadChar(a);
242 }
243 }
244 else {
245 do {
246 m_lastText += a;
247 ReadChar(a);
248 if (m_file.eof() || !m_file.good()) {
249 throw InvalidFileException(CreateLineMsg("End of file encountered when reading string label"));
250 }
251 if (a == '\n') {
252 IncreaseLine();
253 }
254 } while (!isspace(a));
255 }
256
257 return (m_lastToken = TOKEN_TEXT);
258 }
259
260 m_lastText = "";
261 while (!isspace(c) && !m_file.eof()) {
262 m_lastText += c;
263 ReadChar(c);
264 }
265 return (m_lastToken = TOKEN_SYMBOL);
266 }
267
CreateLineMsg(const std::string & msg)268 std::string GameParserState::CreateLineMsg(const std::string &msg)
269 {
270 std::stringstream stream;
271 stream << "line " << m_currentLine << ":" << m_currentColumn << ": " << msg;
272 return stream.str();
273 }
274
275 class TableFilePlayer {
276 public:
277 std::string m_name;
278 Array<std::string> m_strategies;
279 TableFilePlayer *m_next;
280
281 TableFilePlayer(void);
282 };
283
TableFilePlayer(void)284 TableFilePlayer::TableFilePlayer(void)
285 : m_next(0)
286 { }
287
288 class TableFileGame {
289 public:
290 std::string m_title, m_comment;
291 TableFilePlayer *m_firstPlayer, *m_lastPlayer;
292 int m_numPlayers;
293
294 TableFileGame(void);
295 ~TableFileGame();
296
297 void AddPlayer(const std::string &);
NumPlayers(void) const298 int NumPlayers(void) const { return m_numPlayers; }
299 int NumStrategies(int pl) const;
300 std::string GetPlayer(int pl) const;
301 std::string GetStrategy(int pl, int st) const;
302 };
303
TableFileGame(void)304 TableFileGame::TableFileGame(void)
305 : m_firstPlayer(0), m_lastPlayer(0), m_numPlayers(0)
306 { }
307
~TableFileGame()308 TableFileGame::~TableFileGame()
309 {
310 if (m_firstPlayer) {
311 TableFilePlayer *player = m_firstPlayer;
312 while (player) {
313 TableFilePlayer *nextPlayer = player->m_next;
314 delete player;
315 player = nextPlayer;
316 }
317 }
318 }
319
AddPlayer(const std::string & p_name)320 void TableFileGame::AddPlayer(const std::string &p_name)
321 {
322 TableFilePlayer *player = new TableFilePlayer;
323 player->m_name = p_name;
324
325 if (m_firstPlayer) {
326 m_lastPlayer->m_next = player;
327 m_lastPlayer = player;
328 }
329 else {
330 m_firstPlayer = player;
331 m_lastPlayer = player;
332 }
333 m_numPlayers++;
334 }
335
NumStrategies(int p_player) const336 int TableFileGame::NumStrategies(int p_player) const
337 {
338 TableFilePlayer *player = m_firstPlayer;
339 int pl = 1;
340
341 while (player && pl < p_player) {
342 player = player->m_next;
343 pl++;
344 }
345
346 if (!player) {
347 return 0;
348 }
349 else {
350 return player->m_strategies.Length();
351 }
352 }
353
GetPlayer(int p_player) const354 std::string TableFileGame::GetPlayer(int p_player) const
355 {
356 TableFilePlayer *player = m_firstPlayer;
357 int pl = 1;
358
359 while (player && pl < p_player) {
360 player = player->m_next;
361 pl++;
362 }
363
364 if (!player) {
365 return "";
366 }
367 else {
368 return player->m_name;
369 }
370 }
371
GetStrategy(int p_player,int p_strategy) const372 std::string TableFileGame::GetStrategy(int p_player, int p_strategy) const
373 {
374 TableFilePlayer *player = m_firstPlayer;
375 int pl = 1;
376
377 while (player && pl < p_player) {
378 player = player->m_next;
379 pl++;
380 }
381
382 if (!player) {
383 return "";
384 }
385 else {
386 return player->m_strategies[p_strategy];
387 }
388 }
389
ReadPlayers(GameParserState & p_state,TableFileGame & p_data)390 void ReadPlayers(GameParserState &p_state, TableFileGame &p_data)
391 {
392 if (p_state.GetNextToken() != TOKEN_LBRACE) {
393 throw InvalidFileException(
394 p_state.CreateLineMsg("Expecting '{' before players"));
395 }
396
397 while (p_state.GetNextToken() == TOKEN_TEXT) {
398 p_data.AddPlayer(p_state.GetLastText());
399 }
400
401 if (p_state.GetCurrentToken() != TOKEN_RBRACE) {
402 throw InvalidFileException(
403 p_state.CreateLineMsg("Expecting '}' after players"));
404 }
405
406 p_state.GetNextToken();
407 }
408
ReadStrategies(GameParserState & p_state,TableFileGame & p_data)409 void ReadStrategies(GameParserState &p_state, TableFileGame &p_data)
410 {
411 if (p_state.GetCurrentToken() != TOKEN_LBRACE) {
412 throw InvalidFileException(
413 p_state.CreateLineMsg("Expecting '{' before strategies"));
414 }
415 p_state.GetNextToken();
416
417 if (p_state.GetCurrentToken() == TOKEN_LBRACE) {
418 TableFilePlayer *player = p_data.m_firstPlayer;
419
420 while (p_state.GetCurrentToken() == TOKEN_LBRACE) {
421 if (!player) {
422 throw InvalidFileException(p_state.CreateLineMsg(
423 "Not enough players for number of strategy entries"));
424 }
425
426 while (p_state.GetNextToken() == TOKEN_TEXT) {
427 player->m_strategies.Append(p_state.GetLastText());
428 }
429
430 if (p_state.GetCurrentToken() != TOKEN_RBRACE) {
431 throw InvalidFileException(
432 p_state.CreateLineMsg("Expecting '}' after player strategy"));
433 }
434
435 p_state.GetNextToken();
436 player = player->m_next;
437 }
438
439 if (player) {
440 throw InvalidFileException(
441 p_state.CreateLineMsg("Players with undefined strategies"));
442 }
443
444 if (p_state.GetCurrentToken() != TOKEN_RBRACE) {
445 throw InvalidFileException(
446 p_state.CreateLineMsg("Expecting '}' after strategies"));
447 }
448
449 p_state.GetNextToken();
450 }
451 else if (p_state.GetCurrentToken() == TOKEN_NUMBER) {
452 TableFilePlayer *player = p_data.m_firstPlayer;
453
454 while (p_state.GetCurrentToken() == TOKEN_NUMBER) {
455 if (!player) {
456 throw InvalidFileException(p_state.CreateLineMsg(
457 "Not enough players for number of strategy entries"));
458 }
459
460 for (int st = 1; st <= atoi(p_state.GetLastText().c_str()); st++) {
461 player->m_strategies.Append(lexical_cast<std::string>(st));
462 }
463
464 p_state.GetNextToken();
465 player = player->m_next;
466 }
467
468 if (p_state.GetCurrentToken() != TOKEN_RBRACE) {
469 throw InvalidFileException(
470 p_state.CreateLineMsg("Expecting '}' after strategies"));
471 }
472
473 if (player) {
474 throw InvalidFileException(
475 p_state.CreateLineMsg("Players with strategies undefined"));
476 }
477
478 p_state.GetNextToken();
479 }
480 else {
481 throw InvalidFileException(
482 p_state.CreateLineMsg("Unrecognizable strategies format"));
483 }
484 }
485
ParseNfgHeader(GameParserState & p_state,TableFileGame & p_data)486 void ParseNfgHeader(GameParserState &p_state, TableFileGame &p_data)
487 {
488 if (p_state.GetNextToken() != TOKEN_NUMBER ||
489 p_state.GetLastText() != "1") {
490 throw InvalidFileException(
491 p_state.CreateLineMsg("Accepting only NFG version 1"));
492 }
493
494 if (p_state.GetNextToken() != TOKEN_SYMBOL ||
495 (p_state.GetLastText() != "D" && p_state.GetLastText() != "R")) {
496 throw InvalidFileException(
497 p_state.CreateLineMsg("Accepting only NFG D or R data type"));
498 }
499 if (p_state.GetNextToken() != TOKEN_TEXT) {
500 throw InvalidFileException(
501 p_state.CreateLineMsg("Game title missing"));
502 }
503 p_data.m_title = p_state.GetLastText();
504
505 ReadPlayers(p_state, p_data);
506 ReadStrategies(p_state, p_data);
507
508 if (p_state.GetCurrentToken() == TOKEN_TEXT) {
509 // Read optional comment
510 p_data.m_comment = p_state.GetLastText();
511 p_state.GetNextToken();
512 }
513 }
514
515
ReadOutcomeList(GameParserState & p_parser,GameRep * p_nfg)516 void ReadOutcomeList(GameParserState &p_parser, GameRep *p_nfg)
517 {
518 if (p_parser.GetNextToken() == TOKEN_RBRACE) {
519 // Special case: empty outcome list
520 p_parser.GetNextToken();
521 return;
522 }
523
524 if (p_parser.GetCurrentToken() != TOKEN_LBRACE) {
525 throw InvalidFileException(
526 p_parser.CreateLineMsg("Expecting '{' before outcome"));
527 }
528
529 int nOutcomes = 0;
530
531 while (p_parser.GetCurrentToken() == TOKEN_LBRACE) {
532 nOutcomes++;
533 int pl = 1;
534
535 if (p_parser.GetNextToken() != TOKEN_TEXT) {
536 throw InvalidFileException(
537 p_parser.CreateLineMsg("Expecting string for outcome"));
538 }
539
540 GameOutcome outcome;
541 try {
542 outcome = p_nfg->GetOutcome(nOutcomes);
543 }
544 catch (IndexException &) {
545 // It might happen that the file contains more outcomes than
546 // contingencies. If so, just create them on the fly.
547 outcome = p_nfg->NewOutcome();
548 }
549 outcome->SetLabel(p_parser.GetLastText());
550 p_parser.GetNextToken();
551
552 try {
553 while (p_parser.GetCurrentToken() == TOKEN_NUMBER) {
554 outcome->SetPayoff(pl++, p_parser.GetLastText());
555 if (p_parser.GetNextToken() == TOKEN_COMMA) {
556 p_parser.GetNextToken();
557 }
558 }
559 }
560 catch (IndexException &) {
561 // This would be triggered by too many payoffs
562 throw InvalidFileException(
563 p_parser.CreateLineMsg("Exceeded number of players in outcome"));
564 }
565
566 if (pl <= p_nfg->NumPlayers() ||
567 p_parser.GetCurrentToken() != TOKEN_RBRACE) {
568 throw InvalidFileException(
569 p_parser.CreateLineMsg("Insufficient number of players in outcome"));
570 }
571
572 p_parser.GetNextToken();
573 }
574
575 if (p_parser.GetCurrentToken() != TOKEN_RBRACE) {
576 throw InvalidFileException(
577 p_parser.CreateLineMsg("Expecting '}' after outcome"));
578 }
579 p_parser.GetNextToken();
580 }
581
ParseOutcomeBody(GameParserState & p_parser,GameRep * p_nfg)582 void ParseOutcomeBody(GameParserState &p_parser, GameRep *p_nfg)
583 {
584 ReadOutcomeList(p_parser, p_nfg);
585
586 StrategyProfileIterator iter(StrategySupportProfile(static_cast<GameRep *>(p_nfg)));
587
588 while (p_parser.GetCurrentToken() != TOKEN_EOF) {
589 if (p_parser.GetCurrentToken() != TOKEN_NUMBER) {
590 throw InvalidFileException(
591 p_parser.CreateLineMsg("Expecting outcome index"));
592 }
593
594 int outcomeId = atoi(p_parser.GetLastText().c_str());
595 if (outcomeId > 0) {
596 (*iter)->SetOutcome(p_nfg->GetOutcome(outcomeId));
597 }
598 else {
599 (*iter)->SetOutcome(0);
600 }
601 p_parser.GetNextToken();
602 iter++;
603 }
604 }
605
ParsePayoffBody(GameParserState & p_parser,GameRep * p_nfg)606 void ParsePayoffBody(GameParserState &p_parser, GameRep *p_nfg)
607 {
608 StrategyProfileIterator iter(StrategySupportProfile(static_cast<GameRep *>(p_nfg)));
609 int pl = 1;
610
611 while (p_parser.GetCurrentToken() != TOKEN_EOF) {
612 if (p_parser.GetCurrentToken() == TOKEN_NUMBER) {
613 (*iter)->GetOutcome()->SetPayoff(pl, p_parser.GetLastText());
614 }
615 else {
616 throw InvalidFileException(p_parser.CreateLineMsg("Expecting payoff"));
617 }
618
619 if (++pl > p_nfg->NumPlayers()) {
620 iter++;
621 pl = 1;
622 }
623 p_parser.GetNextToken();
624 }
625 }
626
BuildNfg(GameParserState & p_parser,TableFileGame & p_data)627 Game BuildNfg(GameParserState &p_parser, TableFileGame &p_data)
628 {
629 Array<int> dim(p_data.NumPlayers());
630 for (int pl = 1; pl <= dim.Length(); pl++) {
631 dim[pl] = p_data.NumStrategies(pl);
632 }
633
634 GameRep *nfg = NewTable(dim);
635 // Assigning this to the container assures that, if something goes
636 // wrong, the class will automatically be cleaned up
637 Game game = nfg;
638
639 nfg->SetTitle(p_data.m_title);
640 nfg->SetComment(p_data.m_comment);
641
642 for (int pl = 1; pl <= dim.Length(); pl++) {
643 nfg->GetPlayer(pl)->SetLabel(p_data.GetPlayer(pl));
644 for (int st = 1; st <= dim[pl]; st++) {
645 nfg->GetPlayer(pl)->GetStrategy(st)->SetLabel(p_data.GetStrategy(pl,st));
646 }
647 }
648
649 if (p_parser.GetCurrentToken() == TOKEN_LBRACE) {
650 ParseOutcomeBody(p_parser, nfg);
651 }
652 else if (p_parser.GetCurrentToken() == TOKEN_NUMBER) {
653 ParsePayoffBody(p_parser, nfg);
654 }
655 else {
656 throw InvalidFileException(
657 p_parser.CreateLineMsg("Expecting outcome or payoff"));
658 }
659
660 return game;
661 }
662
663
664
665 //=========================================================================
666 // Temporary representation classes
667 //=========================================================================
668
669 class TreeData {
670 public:
671 std::map<int, GameOutcome> m_outcomeMap;
672 std::map<int, GameInfoset> m_chanceInfosetMap;
673 List<std::map<int, GameInfoset> > m_infosetMap;
674
TreeData(void)675 TreeData(void) { }
~TreeData()676 ~TreeData() { }
677 };
678
ReadPlayers(GameParserState & p_state,Game p_game,TreeData & p_treeData)679 void ReadPlayers(GameParserState &p_state,
680 Game p_game, TreeData &p_treeData)
681 {
682 if (p_state.GetNextToken() != TOKEN_LBRACE) {
683 throw InvalidFileException(
684 p_state.CreateLineMsg("Expecting '{' before players"));
685 }
686
687 while (p_state.GetNextToken() == TOKEN_TEXT) {
688 p_game->NewPlayer()->SetLabel(p_state.GetLastText());
689 p_treeData.m_infosetMap.Append(std::map<int, GameInfoset>());
690 }
691
692 if (p_state.GetCurrentToken() != TOKEN_RBRACE) {
693 throw InvalidFileException(
694 p_state.CreateLineMsg("Expecting '}' after players"));
695 }
696 }
697
698 //
699 // Precondition: Parser state should be expecting the integer index
700 // of the outcome in a node entry
701 //
702 // Postcondition: Parser state is past the outcome entry and should be
703 // pointing to the 'c', 'p', or 't' token starting the
704 // next node declaration.
705 //
ParseOutcome(GameParserState & p_state,Game p_game,TreeData & p_treeData,GameNode p_node)706 void ParseOutcome(GameParserState &p_state,
707 Game p_game, TreeData &p_treeData,
708 GameNode p_node)
709 {
710 if (p_state.GetCurrentToken() != TOKEN_NUMBER) {
711 throw InvalidFileException(
712 p_state.CreateLineMsg("Expecting index of outcome"));
713 }
714
715 int outcomeId = atoi(p_state.GetLastText().c_str());
716 p_state.GetNextToken();
717
718 if (p_state.GetCurrentToken() == TOKEN_TEXT) {
719 // This node entry contains information about the outcome
720 GameOutcome outcome;
721 if (p_treeData.m_outcomeMap.count(outcomeId)) {
722 outcome = p_treeData.m_outcomeMap[outcomeId];
723 }
724 else {
725 outcome = p_game->NewOutcome();
726 p_treeData.m_outcomeMap[outcomeId] = outcome;
727 }
728
729 outcome->SetLabel(p_state.GetLastText());
730 p_node->SetOutcome(outcome);
731
732 if (p_state.GetNextToken() != TOKEN_LBRACE) {
733 throw InvalidFileException(
734 p_state.CreateLineMsg("Expecting '{' before outcome"));
735 }
736 p_state.GetNextToken();
737
738 for (int pl = 1; pl <= p_game->NumPlayers(); pl++) {
739 if (p_state.GetCurrentToken() == TOKEN_NUMBER) {
740 outcome->SetPayoff(pl, p_state.GetLastText());
741 }
742 else {
743 throw InvalidFileException(
744 p_state.CreateLineMsg("Payoffs should be numbers"));
745 }
746
747 // Commas are optional between payoffs
748 if (p_state.GetNextToken() == TOKEN_COMMA) {
749 p_state.GetNextToken();
750 }
751 }
752
753 if (p_state.GetCurrentToken() != TOKEN_RBRACE) {
754 throw InvalidFileException(
755 p_state.CreateLineMsg("Expecting '}' after outcome"));
756 }
757 p_state.GetNextToken();
758 }
759 else if (outcomeId != 0) {
760 // The node entry does not contain information about the outcome.
761 // This means the outcome better have been defined already;
762 // if not, raise an error.
763 if (p_treeData.m_outcomeMap.count(outcomeId)) {
764 p_node->SetOutcome(p_treeData.m_outcomeMap[outcomeId]);
765 }
766 else {
767 throw InvalidFileException(
768 p_state.CreateLineMsg("Outcome not defined"));
769 }
770 }
771 }
772
773 void ParseNode(GameParserState &p_state, Game p_game, GameNode p_node,
774 TreeData &p_treeData);
775
776 //
777 // Precondition: parser state is expecting the node label
778 //
779 // Postcondition: parser state is pointing at the 'c', 'p', or 't'
780 // beginning the next node entry
781 //
ParseChanceNode(GameParserState & p_state,Game p_game,GameNode p_node,TreeData & p_treeData)782 void ParseChanceNode(GameParserState &p_state,
783 Game p_game, GameNode p_node, TreeData &p_treeData)
784 {
785 if (p_state.GetNextToken() != TOKEN_TEXT) {
786 throw InvalidFileException(p_state.CreateLineMsg("Expecting label"));
787 }
788 p_node->SetLabel(p_state.GetLastText());
789
790 if (p_state.GetNextToken() != TOKEN_NUMBER) {
791 throw InvalidFileException(p_state.CreateLineMsg("Expecting infoset id"));
792 }
793
794 int infosetId = atoi(p_state.GetLastText().c_str());
795 GameInfoset infoset;
796 if (p_treeData.m_chanceInfosetMap.count(infosetId)) {
797 infoset = p_treeData.m_chanceInfosetMap[infosetId];
798 }
799
800 p_state.GetNextToken();
801
802 if (p_state.GetCurrentToken() == TOKEN_TEXT) {
803 // Information set data is specified
804 List<std::string> actions, probs;
805 std::string label = p_state.GetLastText();
806
807 if (p_state.GetNextToken() != TOKEN_LBRACE) {
808 throw InvalidFileException(
809 p_state.CreateLineMsg("Expecting '{' before information set data"));
810 }
811 p_state.GetNextToken();
812 do {
813 if (p_state.GetCurrentToken() != TOKEN_TEXT) {
814 throw InvalidFileException(p_state.CreateLineMsg("Expecting action"));
815 }
816 actions.Append(p_state.GetLastText());
817
818 p_state.GetNextToken();
819
820 if (p_state.GetCurrentToken() == TOKEN_NUMBER) {
821 probs.Append(p_state.GetLastText());
822 }
823 else {
824 throw InvalidFileException(p_state.CreateLineMsg("Expecting probability"));
825 }
826
827 p_state.GetNextToken();
828 } while (p_state.GetCurrentToken() != TOKEN_RBRACE);
829 p_state.GetNextToken();
830
831 if (!infoset) {
832 infoset = p_node->AppendMove(p_game->GetChance(), actions.Length());
833 p_treeData.m_chanceInfosetMap[infosetId] = infoset;
834 infoset->SetLabel(label);
835 for (int act = 1; act <= actions.Length(); act++) {
836 infoset->GetAction(act)->SetLabel(actions[act]);
837 infoset->SetActionProb(act, probs[act]);
838 }
839 }
840 else {
841 // TODO: Verify actions match up to previous specifications
842 p_node->AppendMove(infoset);
843 }
844 }
845 else if (infoset) {
846 p_node->AppendMove(infoset);
847 }
848 else {
849 // Referencing an undefined infoset is an error
850 throw InvalidFileException(
851 p_state.CreateLineMsg("Referencing an undefined infoset"));
852 }
853
854 ParseOutcome(p_state, p_game, p_treeData, p_node);
855
856 for (int i = 1; i <= p_node->NumChildren(); i++) {
857 ParseNode(p_state, p_game, p_node->GetChild(i), p_treeData);
858 }
859 }
860
ParsePersonalNode(GameParserState & p_state,Game p_game,GameNode p_node,TreeData & p_treeData)861 void ParsePersonalNode(GameParserState &p_state,
862 Game p_game, GameNode p_node, TreeData &p_treeData)
863 {
864 if (p_state.GetNextToken() != TOKEN_TEXT) {
865 throw InvalidFileException(p_state.CreateLineMsg("Expecting label"));
866 }
867 p_node->SetLabel(p_state.GetLastText());
868
869 if (p_state.GetNextToken() != TOKEN_NUMBER) {
870 throw InvalidFileException(p_state.CreateLineMsg("Expecting player id"));
871 }
872 int playerId = atoi(p_state.GetLastText().c_str());
873 // This will throw an exception if the player ID is not valid
874 GamePlayer player = p_game->GetPlayer(playerId);
875 std::map<int, GameInfoset> &infosetMap = p_treeData.m_infosetMap[playerId];
876
877 if (p_state.GetNextToken() != TOKEN_NUMBER) {
878 throw InvalidFileException(p_state.CreateLineMsg("Expecting infoset id"));
879 }
880 int infosetId = atoi(p_state.GetLastText().c_str());
881 GameInfoset infoset;
882 if (infosetMap.count(infosetId)) {
883 infoset = infosetMap[infosetId];
884 }
885
886 p_state.GetNextToken();
887
888 if (p_state.GetCurrentToken() == TOKEN_TEXT) {
889 // Information set data is specified
890 List<std::string> actions;
891 std::string label = p_state.GetLastText();
892
893 if (p_state.GetNextToken() != TOKEN_LBRACE) {
894 throw InvalidFileException(
895 p_state.CreateLineMsg("Expecting '{' before information set data"));
896 }
897 p_state.GetNextToken();
898 do {
899 if (p_state.GetCurrentToken() != TOKEN_TEXT) {
900 throw InvalidFileException(
901 p_state.CreateLineMsg("Expecting action"));
902 }
903 actions.Append(p_state.GetLastText());
904
905 p_state.GetNextToken();
906 } while (p_state.GetCurrentToken() != TOKEN_RBRACE);
907 p_state.GetNextToken();
908
909 if (!infoset) {
910 infoset = p_node->AppendMove(player, actions.Length());
911 infosetMap[infosetId] = infoset;
912 infoset->SetLabel(label);
913 for (int act = 1; act <= actions.Length(); act++) {
914 infoset->GetAction(act)->SetLabel(actions[act]);
915 }
916 }
917 else {
918 // TODO: Verify actions match up to previous specifications
919 p_node->AppendMove(infoset);
920 }
921 }
922 else if (infoset) {
923 p_node->AppendMove(infoset);
924 }
925 else {
926 // Referencing an undefined infoset is an error
927 throw InvalidFileException(
928 p_state.CreateLineMsg("Referencing an undefined infoset"));
929 }
930
931 ParseOutcome(p_state, p_game, p_treeData, p_node);
932
933 for (int i = 1; i <= p_node->NumChildren(); i++) {
934 ParseNode(p_state, p_game, p_node->GetChild(i), p_treeData);
935 }
936 }
937
ParseTerminalNode(GameParserState & p_state,Game p_game,GameNode p_node,TreeData & p_treeData)938 void ParseTerminalNode(GameParserState &p_state,
939 Game p_game, GameNode p_node, TreeData &p_treeData)
940 {
941 if (p_state.GetNextToken() != TOKEN_TEXT) {
942 throw InvalidFileException(p_state.CreateLineMsg("Expecting label"));
943 }
944
945 p_node->SetLabel(p_state.GetLastText());
946
947 p_state.GetNextToken();
948 ParseOutcome(p_state, p_game, p_treeData, p_node);
949 }
950
ParseNode(GameParserState & p_state,Game p_game,GameNode p_node,TreeData & p_treeData)951 void ParseNode(GameParserState &p_state, Game p_game, GameNode p_node,
952 TreeData &p_treeData)
953 {
954 if (p_state.GetLastText() == "c") {
955 ParseChanceNode(p_state, p_game, p_node, p_treeData);
956 }
957 else if (p_state.GetLastText() == "p") {
958 ParsePersonalNode(p_state, p_game, p_node, p_treeData);
959 }
960 else if (p_state.GetLastText() == "t") {
961 ParseTerminalNode(p_state, p_game, p_node, p_treeData);
962 }
963 else {
964 throw InvalidFileException(p_state.CreateLineMsg("Invalid type of node"));
965 }
966 }
967
ParseEfg(GameParserState & p_state,Game p_game,TreeData & p_treeData)968 void ParseEfg(GameParserState &p_state, Game p_game, TreeData &p_treeData)
969 {
970 if (p_state.GetNextToken() != TOKEN_NUMBER ||
971 p_state.GetLastText() != "2") {
972 throw InvalidFileException(
973 p_state.CreateLineMsg("Accepting only EFG version 2"));
974 }
975
976 if (p_state.GetNextToken() != TOKEN_SYMBOL ||
977 (p_state.GetLastText() != "D" && p_state.GetLastText() != "R")) {
978 throw InvalidFileException(
979 p_state.CreateLineMsg("Accepting only EFG R or D data type"));
980 }
981 if (p_state.GetNextToken() != TOKEN_TEXT) {
982 throw InvalidFileException(p_state.CreateLineMsg("Game title missing"));
983 }
984 p_game->SetTitle(p_state.GetLastText());
985
986 ReadPlayers(p_state, p_game, p_treeData);
987
988 if (p_state.GetNextToken() == TOKEN_TEXT) {
989 // Read optional comment
990 p_game->SetComment(p_state.GetLastText());
991 p_state.GetNextToken();
992 }
993
994 ParseNode(p_state, p_game, p_game->GetRoot(), p_treeData);
995 }
996
997 } // end of anonymous namespace
998
999
1000 #include "gambit/tinyxml.h"
1001
1002 namespace Gambit {
1003
1004 class GameXMLSavefile {
1005 private:
1006 TiXmlDocument doc;
1007
1008 public:
1009 GameXMLSavefile(const std::string &p_xml);
~GameXMLSavefile()1010 ~GameXMLSavefile() { }
1011
1012 Game GetGame(void) const;
1013 };
1014
GameXMLSavefile(const std::string & p_xml)1015 GameXMLSavefile::GameXMLSavefile(const std::string &p_xml)
1016 {
1017 doc.Parse(p_xml.c_str());
1018 if (doc.Error()) {
1019 throw InvalidFileException("Not a valid XML document");
1020 }
1021 }
1022
GetGame(void) const1023 Game GameXMLSavefile::GetGame(void) const
1024 {
1025 const TiXmlNode *docroot = doc.FirstChild("gambit:document");
1026 if (!docroot) {
1027 throw InvalidFileException("Not a Gambit game savefile document");
1028 }
1029
1030 const TiXmlNode *game = docroot->FirstChild("game");
1031 if (!game) {
1032 throw InvalidFileException("No game representation found in document");
1033 }
1034
1035 const TiXmlNode *efgfile = game->FirstChild("efgfile");
1036 if (efgfile) {
1037 std::istringstream s(efgfile->FirstChild()->Value());
1038 return ReadGame(s);
1039 }
1040
1041 const TiXmlNode *nfgfile = game->FirstChild("nfgfile");
1042 if (nfgfile) {
1043 std::istringstream s(nfgfile->FirstChild()->Value());
1044 return ReadGame(s);
1045 }
1046
1047 throw InvalidFileException("No game representation found in document");
1048 }
1049
1050 //=========================================================================
1051 // ReadGame: Global visible function to read an .efg or .nfg file
1052 //=========================================================================
1053
ReadGame(std::istream & p_file)1054 Game ReadGame(std::istream &p_file) throw (InvalidFileException)
1055 {
1056 std::stringstream buffer;
1057 buffer << p_file.rdbuf();
1058 try {
1059 GameXMLSavefile doc(buffer.str());
1060 return doc.GetGame();
1061 }
1062 catch (InvalidFileException) {
1063 buffer.seekg(0, std::ios::beg);
1064 }
1065
1066 GameParserState parser(buffer);
1067 try {
1068 if (parser.GetNextToken() != TOKEN_SYMBOL) {
1069 throw InvalidFileException(parser.CreateLineMsg("Expecting file type"));
1070 }
1071
1072 if (parser.GetLastText() == "NFG") {
1073 TableFileGame data;
1074 ParseNfgHeader(parser, data);
1075 return BuildNfg(parser, data);
1076 }
1077 else if (parser.GetLastText() == "EFG") {
1078 TreeData treeData;
1079 Game game = NewTree();
1080 dynamic_cast<GameTreeRep &>(*game).SetCanonicalization(false);
1081 ParseEfg(parser, game, treeData);
1082 dynamic_cast<GameTreeRep &>(*game).SetCanonicalization(true);
1083 return game;
1084 }
1085 else if (parser.GetLastText() == "#AGG") {
1086 return GameAggRep::ReadAggFile(buffer);
1087 }
1088 else if (parser.GetLastText() == "#BAGG") {
1089 return GameBagentRep::ReadBaggFile(buffer);
1090 }
1091 else {
1092 throw InvalidFileException("Tokens 'EFG' or 'NFG' or '#AGG' or '#BAGG' expected at start of file");
1093 }
1094 }
1095 catch (std::exception &ex) {
1096 throw InvalidFileException(ex.what());
1097 }
1098 }
1099
1100 } // end namespace Gambit
1101