improve error handling in lisp parser
[supertux.git] / src / lisp / parser.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include <sstream>
23 #include <stdexcept>
24 #include <fstream>
25 #include <cassert>
26 #include <iostream>
27
28 #include "tinygettext/tinygettext.hpp"
29 #include "physfs/physfs_stream.hpp"
30 #include "parser.hpp"
31 #include "lisp.hpp"
32
33 namespace lisp
34 {
35
36 Parser::Parser(bool translate)
37   : lexer(0), dictionary_manager(0), dictionary(0)
38 {
39   if(translate) {
40     dictionary_manager = new TinyGetText::DictionaryManager();
41     dictionary_manager->set_charset("UTF-8");
42   }
43 }
44
45 Parser::~Parser()
46 {
47   delete lexer;
48   delete dictionary_manager;
49 }
50
51 static std::string dirname(std::string filename)
52 {
53   std::string::size_type p = filename.find_last_of('/');
54   if(p == std::string::npos)
55     return "";
56
57   return filename.substr(0, p+1);
58 }
59
60 Lisp*
61 Parser::parse(const std::string& filename)
62 {
63   IFileStreambuf ins(filename);
64   std::istream in(&ins);
65
66   this->filename = filename;
67   if(!in.good()) {
68     std::stringstream msg;
69     msg << "Parser problem: Couldn't open file '" << filename << "'.";
70     throw std::runtime_error(msg.str());
71   }
72
73   if(dictionary_manager) {
74     dictionary_manager->add_directory(dirname(filename));
75     dictionary = & (dictionary_manager->get_dictionary());
76   }
77
78   return parse(in);
79 }
80
81 Lisp*
82 Parser::parse(std::istream& stream)
83 {
84   delete lexer;
85   lexer = new Lexer(stream);
86
87   token = lexer->getNextToken();
88   Lisp* result = new Lisp(Lisp::TYPE_CONS);
89   result->v.cons.car = read();
90   result->v.cons.cdr = 0;
91
92   delete lexer;
93   lexer = 0;
94
95   return result;
96 }
97
98 void
99 Parser::parse_error(const char* msg)
100 {
101   std::stringstream emsg;
102   emsg << "Parse Error at '" << filename << "' line " << lexer->getLineNumber()
103           << ": " << msg;
104   throw std::runtime_error(emsg.str());
105 }
106
107 Lisp*
108 Parser::read()
109 {
110   Lisp* result;
111   switch(token) {
112     case Lexer::TOKEN_EOF: {
113           parse_error("Unexpected EOF.");
114     }
115     case Lexer::TOKEN_CLOSE_PAREN: {
116       parse_error("Unexpected ')'.");
117     }
118     case Lexer::TOKEN_OPEN_PAREN: {
119       result = new Lisp(Lisp::TYPE_CONS);
120
121       token = lexer->getNextToken();
122       if(token == Lexer::TOKEN_CLOSE_PAREN) {
123         result->v.cons.car = 0;
124         result->v.cons.cdr = 0;
125         break;
126       }
127
128       if(token == Lexer::TOKEN_SYMBOL &&
129           strcmp(lexer->getString(), "_") == 0) {
130         // evaluate translation function (_ str) in place here
131         token = lexer->getNextToken();
132         if(token != Lexer::TOKEN_STRING)
133                   parse_error("Expected string after '(_'");
134
135         result = new Lisp(Lisp::TYPE_STRING);
136         if(dictionary) {
137           std::string translation = dictionary->translate(lexer->getString());
138           result->v.string = new char[translation.size()+1];
139           memcpy(result->v.string, translation.c_str(), translation.size()+1);
140         } else {
141           size_t len = strlen(lexer->getString()) + 1;
142           result->v.string = new char[len];
143           memcpy(result->v.string, lexer->getString(), len);
144         }
145         token = lexer->getNextToken();
146         if(token != Lexer::TOKEN_CLOSE_PAREN)
147                   parse_error("Expected ')' after '(_ string'");
148         break;
149       }
150
151       Lisp* cur = result;
152       do {
153         cur->v.cons.car = read();
154         if(token == Lexer::TOKEN_CLOSE_PAREN) {
155           cur->v.cons.cdr = 0;
156           break;
157         }
158         cur->v.cons.cdr = new Lisp(Lisp::TYPE_CONS);
159         cur = cur->v.cons.cdr;
160       } while(1);
161
162       break;
163     }
164     case Lexer::TOKEN_SYMBOL: {
165       result = new Lisp(Lisp::TYPE_SYMBOL);
166       size_t len = strlen(lexer->getString()) + 1;
167       result->v.string = new char[len];
168       memcpy(result->v.string, lexer->getString(), len);
169       break;
170     }
171     case Lexer::TOKEN_STRING: {
172       result = new Lisp(Lisp::TYPE_STRING);
173       size_t len = strlen(lexer->getString()) + 1;
174       result->v.string = new char[len];
175       memcpy(result->v.string, lexer->getString(), len);
176       break;
177     }
178     case Lexer::TOKEN_INTEGER:
179       result = new Lisp(Lisp::TYPE_INTEGER);
180       sscanf(lexer->getString(), "%d", &result->v.integer);
181       break;
182     case Lexer::TOKEN_REAL:
183       result = new Lisp(Lisp::TYPE_REAL);
184       sscanf(lexer->getString(), "%f", &result->v.real);
185       break;
186     case Lexer::TOKEN_TRUE:
187       result = new Lisp(Lisp::TYPE_BOOLEAN);
188       result->v.boolean = true;
189       break;
190     case Lexer::TOKEN_FALSE:
191       result = new Lisp(Lisp::TYPE_BOOLEAN);
192       result->v.boolean = false;
193       break;
194
195     default:
196       // this should never happen
197       assert(false);
198   }
199
200   token = lexer->getNextToken();
201   return result;
202 }
203
204 } // end of namespace lisp