I'm working on a homework writing a toy interpreter. All the expressions in this language are prefix expressions. The language has a lot of lexical tokens and I wrote code as follows:
Value *expression() {
std::string token;
cin >> token;
if (token == "make")
return make();
else if (token == "thing")
return thing();
else if (token == "print")
return print();
else if (token == "read")
return read();
else if (token == "add")
return new Number(*expression() + *expression());
else if (token == "sub")
return new Number(*expression() - *expression());
else if (token == "mul")
return new Number(*expression() * *expression());
else if (token == "div")
return new Number(*expression() / *expression());
else if (token == "mod")
return new Number(int(*expression()) % int(*expression()));
else if (token == "true")
return new Bool(true);
else if (token == "false")
return new Bool(false);
else if (token[0] == '"') // String
return new Word(token.substr(1));
else if (token[0] == ':')
return thing(token.substr(1));
else // Number
return new Number(std::stod(token));
}
int main() {
while (true) {
try {
auto res = expression();
} catch (const std::exception &e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}
return 0;
}
But I think it's kind of ugly to have such a long (and will be longer) if-else chain, and maybe it will cost much time to compare along this chain. I don't know whether there will be some optimizations done by the compiler.
I'm wondering if I have some better way to organize my code, or is this if-else chain method already good enough?
The complete compilable code is:
#include <exception>
#include <iostream>
#include <string>
#include <unordered_map>
using std::cin;
using std::cout;
using std::endl;
class Value {
public:
virtual operator double() = 0;
virtual std::string toString() = 0;
};
class Number : public Value {
double value;
public:
Number(double d) : value(d) {}
operator double() { return value; }
std::string toString() { return std::to_string(value); }
};
class Word : public Value {
std::string value;
public:
Word(const std::string &str) : value(str) {}
Word(std::string &&str) : value(str) {}
operator double() { return std::stod(value); }
std::string get() { return value; }
std::string toString() { return value; }
};
class Bool : public Value {
bool value;
public:
Bool(bool b) : value(b) {}
Bool(const std::string &str) {
if (str == "true")
value = true;
else if (str == "false")
value = false;
else
throw std::invalid_argument{str + " is not a valid Bool value"};
}
operator double() { throw std::invalid_argument{"Bool is not convertable to Number。"}; }
std::string toString() { return value ? "true" : "false"; }
};
template <typename T, typename U> T downcastOrDie(U *ptr) {
auto res = dynamic_cast<T>(ptr);
if (!res) throw std::invalid_argument{"dynamic cast failed"};
return res;
}
std::unordered_map<std::string, Value *> symbolTbl;
void program();
Value *expression();
Value *make();
Value *thing();
Value *thing(const std::string &name);
Value *print();
Value *read();
Value *expression() {
std::string token;
cin >> token;
if (token == "make")
return make();
else if (token == "thing")
return thing();
else if (token == "print")
return print();
else if (token == "read")
return read();
else if (token == "add")
return new Number(*expression() + *expression());
else if (token == "sub")
return new Number(*expression() - *expression());
else if (token == "mul")
return new Number(*expression() * *expression());
else if (token == "div")
return new Number(*expression() / *expression());
else if (token == "mod")
return new Number(int(*expression()) % int(*expression()));
else if (token == "true")
return new Bool(true);
else if (token == "false")
return new Bool(false);
else if (token[0] == '"') // String
return new Word(token.substr(1));
else if (token[0] == ':')
return thing(token.substr(1));
else // Number
return new Number(std::stod(token));
}
Value *make() {
auto name = expression();
auto value = expression();
symbolTbl[downcastOrDie<Word *>(name)->get()] = value;
return value;
}
Value *thing() { return thing(downcastOrDie<Word *>(expression())->get()); }
Value *thing(const std::string &name) { return symbolTbl[name]; }
Value *print() {
auto val = expression();
cout << val->toString() << endl;
return val;
}
bool isDouble(const std::string &str) {
bool hasDecimalPoint = false;
if (str.length() == 0) return false;
auto it = str.begin();
if (*it == '-') ++it;
for (; it != str.end(); ++it) {
if (*it == '.') {
if (hasDecimalPoint)
return false;
else
hasDecimalPoint = true;
} else if (!std::isdigit(*it))
return false;
}
return true;
}
Value *read() {
std::string input;
cin >> input;
if (isDouble(input)) {
return new Number(std::stod(input));
} else {
return new Word(input);
}
}
int main() {
while (true) {
try {
auto res = expression();
} catch (const std::exception &e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}
return 0;
}
switchon. Or you can return a label that yougoto(don’t do that one though). Though looking at your list, which contains different levels of operants and expressions, it seems to me you need to re-read the chapter on parsing in your programming book. Actually we don’t write parsers by hand, that process was already automated back in the 1970’s (withyacc). \$\endgroup\$flexandbisonto do this. \$\endgroup\$