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