InFact
Interpreter and factory for easily creating C++ objects at run-time
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
interpreter.cc
Go to the documentation of this file.
1 // Copyright 2014, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 // -----------------------------------------------------------------------------
30 //
31 //
35 
36 #include <sstream>
37 
38 #include "error.h"
39 #include "interpreter.h"
40 
41 using namespace std;
42 
43 namespace infact {
44 
45 using std::ostringstream;
46 
47 // TODO(dbikel): Use std::make_unique here when we start using C++14.
48 unique_ptr<istream>
49 DefaultIStreamBuilder::Build(const string &filename,
50  std::ios_base::openmode mode) const {
51  return unique_ptr<istream>(new std::ifstream(filename, mode));
52 }
53 
54 bool
55 Interpreter::IsAbsolute(const string &filename) const {
56  return filename.length() > 0 && filename[0] == '/';
57 }
58 
59 bool
60 Interpreter::CanReadFile(const string &filename) const {
61  unique_ptr<istream> file = std::move(istream_builder_->Build(filename));
62  return file->good();
63 }
64 
65 bool
66 Interpreter::CanReadFile(const string &f1, const string &f2,
67  string *filename) const {
68  if (CanReadFile(f1)) {
69  *filename = f1;
70  return true;
71  } else if (CanReadFile(f2)) {
72  *filename = f2;
73  return true;
74  } else {
75  return false;
76  }
77 }
78 
79 bool
80 Interpreter::HasCycle(const string &filename,
81  const vector<string> &filenames) const {
82  for (const string &f : filenames) {
83  if (filename == f) {
84  return true;
85  }
86  }
87  return false;
88 }
89 
90 void
91 Interpreter::Eval(const string &filename) {
92  if (!CanReadFile(filename)) {
93  ostringstream err_ss;
94  err_ss << "infact::Interpreter: error: cannot read file "
95  << "\"" << filename << "\" (or file does not exist)\n";
96  Error(err_ss.str());
97  }
98  EvalFile(filename);
99 }
100 
101 // This is the implementation of the private EvalFile method, used
102 // both the top-level Eval method above, as well as for importing
103 // files.
104 void
105 Interpreter::EvalFile(const string &filename) {
106  filenames_.push_back(filename);
107  unique_ptr<istream> file =
108  std::move(istream_builder_->Build(curr_filename()));
109  Eval(*file);
110  filenames_.pop_back();
111 }
112 
113 void
114 Interpreter::Import(StreamTokenizer &st) {
115  // Consume reserved word "import".
116  st.Next();
117 
118  if (st.PeekTokenType() != StreamTokenizer::STRING) {
119  string expected_type =
120  string(StreamTokenizer::TypeName(StreamTokenizer::RESERVED_WORD));
121  string found_type = StreamTokenizer::TypeName(st.PeekTokenType());
122  WrongTokenTypeError(st, st.PeekTokenStart(), expected_type, found_type,
123  st.Peek());
124  }
125 
126  if (debug_ >= 1) {
127  std::cerr << "infact::Interpreter: from file \"" << curr_filename()
128  << "\" importing \"" << st.Peek() << "\"\n";
129  }
130 
131  // Grab the string naming the file to be imported.
132  string original_import_filename = st.Next();
133  string relative_import_filename = original_import_filename;
134 
135  // Test to see if the named file exists. If the path is not
136  // absolute, we try to get the dirname of current file, if it
137  // exists, and create a relative path, which takes precedence
138  // over a path relative to the current working directory.
139  bool relative = false;
140  if (!IsAbsolute(original_import_filename)) {
141  size_t slash_pos = curr_filename().rfind('/');
142  if (slash_pos != string::npos) {
143  string dirname = curr_filename().substr(0, slash_pos);
144  relative_import_filename = dirname + '/' + original_import_filename;
145  relative = true;
146  }
147  }
148 
149  string import_filename;
150  if (!CanReadFile(relative_import_filename, original_import_filename,
151  &import_filename)) {
152  ostringstream err_ss;
153  err_ss << "infact::Interpreter: " << filestack(st, st.tellg())
154  << "error: cannot read file \"";
155  if (relative) {
156  err_ss << relative_import_filename << "\" or \"";
157  }
158  err_ss << original_import_filename << "\" (or file does not exist)\n";
159  Error(err_ss.str());
160  } else {
161  if (debug_ >= 1) {
162  std::cerr << "infact::Interpreter: tested paths \""
163  << relative_import_filename << "\" and \""
164  << original_import_filename << "\" and found that \""
165  << import_filename << "\" exists and is readable\n";
166  }
167  }
168 
169  if (HasCycle(import_filename, filenames_)) {
170  ostringstream err_ss;
171  err_ss << "infact::Interpreter: " << filestack(st, st.tellg())
172  << "attempted import of file \"" << import_filename << "\" "
173  << "from file \"" << curr_filename() << "\" introduces cycle";
174  Error(err_ss.str());
175  }
176 
177  // Finally, evaluate file using the private EvalFile method. The imported
178  // file gets interpreted using the current Environment.
179  EvalFile(import_filename);
180 
181  if (st.Peek() != ";") {
182  WrongTokenError(st, st.PeekTokenStart(), ";", st.Peek(),
183  st.PeekTokenType());
184  }
185 
186  // Consume semicolon.
187  st.Next();
188 }
189 
190 void
191 Interpreter::Eval(StreamTokenizer &st) {
192  // Keeps reading import or assignment statements until there are no
193  // more tokens.
194  while (st.PeekTokenType() != StreamTokenizer::EOF_TYPE) {
195 #ifdef INFACT_THROW_EXCEPTIONS
196  try {
197 #endif
198  StreamTokenizer::TokenType token_type = st.PeekTokenType();
199  // First, see if we have an import statement.
200  if (token_type == StreamTokenizer::RESERVED_WORD && st.Peek() == "import") {
201  Import(st);
202  // Now continue this loop reading either assignment or import statements.
203  continue;
204  }
205  // Read variable name or type specifier.
206  VarMapBase *varmap = env_->GetVarMapForType(st.Peek());
207  bool is_type_specifier = varmap != nullptr;
208  if (token_type != StreamTokenizer::IDENTIFIER && !is_type_specifier) {
209  string expected_type =
210  string(StreamTokenizer::TypeName(StreamTokenizer::IDENTIFIER)) +
211  " or type specifier";
212  string found_type = StreamTokenizer::TypeName(token_type);
213  WrongTokenTypeError(st, st.PeekTokenStart(), expected_type, found_type,
214  st.Peek());
215  }
216 
217  string type = "";
218  if (is_type_specifier) {
219  // Consume and remember the type specifier.
220  st.Next(); // Explicit type could be a concrete type.
221  type = varmap->Name(); // Remember the abstract type.
222 
223  // Check that next token is a variable name.
224  StreamTokenizer::TokenType token_type = st.PeekTokenType();
225  if (token_type != StreamTokenizer::IDENTIFIER) {
226  WrongTokenTypeError(st, st.PeekTokenStart(),
227  StreamTokenizer::IDENTIFIER, token_type, st.Peek());
228  }
229  }
230 
231  string varname = st.Next();
232 
233  // Next, read equals sign.
234  token_type = st.PeekTokenType();
235  if (st.Peek() != "=") {
236  WrongTokenError(st, st.PeekTokenStart(), "=", st.Peek(),
237  st.PeekTokenType());
238  }
239 
240  // Consume equals sign.
241  st.Next();
242 
243  if (st.PeekTokenType() == StreamTokenizer::EOF_TYPE) {
244  ostringstream err_ss;
245  err_ss << "infact::Interpreter: " << filestack(st, st.tellg())
246  << "error: unexpected EOF";
247  Error(err_ss.str());
248  }
249 
250  // Consume and set the value for this variable in the environment.
251  env_->ReadAndSet(varname, st, type);
252 
253  token_type = st.PeekTokenType();
254  if (st.Peek() != ";") {
255  WrongTokenError(st, st.PeekTokenStart(), ";", st.Peek(),
256  st.PeekTokenType());
257  }
258  // Consume semicolon.
259  st.Next();
260 #ifdef INFACT_THROW_EXCEPTIONS
261  }
262  catch (std::runtime_error &e) {
263  cerr << "infact::Interpreter: caught exception\n"
264  << filestack(st, st.tellg())
265  << "==================\n"
266  << "Exception message:\n"
267  << "==================\n" << e.what() << "\n"
268  << endl;
269  // For now, we simply give up.
270  break;
271  }
272 #endif
273  }
274 }
275 
276 string
277 Interpreter::filestack(StreamTokenizer &st, size_t pos) const {
278  ostringstream message;
279  message << "in file \"" << curr_filename() << "\" "
280  << "(stream pos: " << pos << ")\n";
281  auto it = filenames_.rbegin();
282  if (it != filenames_.rend()) {
283  ++it;
284  }
285  for (; it != filenames_.rend(); ++it) {
286  const string &filename = *it;
287  message << "\timported from \"" << filename << "\"\n";
288  }
289  string line = st.line();
290  message << "here:\n" << line << "\n";
291  if (st.PeekPrevTokenLineStart() <= pos) {
292  for (int i = pos - st.PeekPrevTokenLineStart(); i > 0; --i) {
293  message << " ";
294  }
295  message << "^\n";
296  }
297  return message.str();
298 }
299 
300 void
301 Interpreter::WrongTokenError(StreamTokenizer &st,
302  size_t pos,
303  const string &expected,
304  const string &found,
305  StreamTokenizer::TokenType found_type) const {
306  // If possible, consume the wrong token.
307  if (st.HasNext()) {
308  st.Next();
309  }
310  ostringstream err_ss;
311  err_ss << "infact::Interpreter: " << filestack(st, pos)
312  << "expected token \"" << expected << "\" but found \"" << found
313  << "\" (token type: " << StreamTokenizer::TypeName(found_type)
314  << ")";
315  Error(err_ss.str());
316 }
317 
318 void
319 Interpreter::WrongTokenTypeError(StreamTokenizer &st,
320  size_t pos,
321  StreamTokenizer::TokenType expected,
322  StreamTokenizer::TokenType found,
323  const string &token) const {
324  WrongTokenTypeError(st, pos,
325  StreamTokenizer::TypeName(expected),
326  StreamTokenizer::TypeName(found),
327  token);
328 }
329 
330 void
331 Interpreter::WrongTokenTypeError(StreamTokenizer &st,
332  size_t pos,
333  const string &expected_type,
334  const string &found_type,
335  const string &token) const {
336  // If possible, consume the wrong token.
337  if (st.HasNext()) {
338  st.Next();
339  }
340  ostringstream err_ss;
341  err_ss << "infact::Interpreter: " << filestack(st, pos)
342  << "expected token type " << expected_type
343  << " but found " << found_type
344  << "; token=\"" << token << "\"";
345  Error(err_ss.str());
346 }
347 
348 } // namespace infact
Provides an interpreter for assigning primitives and Factory-constructible objects to named variables...
Provides an error handling function that optionally throws an exception.
void Error(const std::string &message)
Reports an error encountered during parsing and/or construction of an object.
Definition: error.cc:47