This is a simple game I'm writing to test the idea of using games to teach C++. https://learncodethehardway.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
5.1 KiB

/*
* Same code as ex06.cpp but I use auto everywhere I could to
* see how it works.
*/
#include <iostream>
#include <queue>
using namespace std;
class Error {
public:
string message;
Error(string m) : message{m} {};
};
enum class TokenKind {
SEMI_COLON,
QUIT,
LPAREN,
RPAREN,
PLUS,
MINUS,
MULT,
DIV,
NUMBER,
NAME,
EQ,
ERROR
};
class Token {
public:
TokenKind kind = TokenKind::ERROR;
double value = 0.0;
string name = "";
Token(TokenKind k) : kind(k) {}
Token(TokenKind k, double v) : kind{k}, value{v} {}
Token(TokenKind k, string n) : kind{k}, name{n}{}
};
class TokenStream {
public:
auto get() {
if(!buffer.empty()) {
auto first = buffer.front();
buffer.pop();
return first;
}
char ch = 0;
if(!(cin >> ch)) throw Error{"no input"};
switch(ch) {
case ';': return Token{TokenKind::SEMI_COLON};
case 'q': return Token{TokenKind::QUIT};
case '(': return Token{TokenKind::LPAREN};
case ')': return Token{TokenKind::RPAREN};
case '+': return Token{TokenKind::PLUS};
case '-': return Token{TokenKind::MINUS};
case '*': return Token{TokenKind::MULT};
case '/': return Token{TokenKind::DIV};
case '.':
case '0': // fallthrough
case '1': // fallthrough
case '2': // fallthrough
case '3': // fallthrough
case '4':
case '5': // fallthrough
case '6': // fallthrough
case '7': // fallthrough
case '8': // fallthrough
case '9': // falltrhough
{
cin.putback(ch);
auto val = 0.0;
cin >> val;
return Token{TokenKind::NUMBER, val};
}
case '=':
return Token{TokenKind::EQ};
default:
if(isalpha(ch)) {
cin.putback(ch);
string s;
cin >> s;
return Token{TokenKind::NAME, s};
} else {
throw Error{"bad token"};
}
}
}
void putback(Token t) {
buffer.push(t);
}
private:
queue<Token> buffer;
};
class Variable {
public:
string name;
double value;
};
vector<Variable> var_table;
double get_value(string s) {
for(const Variable& v : var_table) {
if(v.name == s) {
return v.value;
}
}
throw Error{"variable not found"};
}
void set_value(string s, double d)
{
for(Variable& v : var_table) {
if(v.name == s) {
v.value = d;
return;
}
}
// variable not found make it
var_table.push_back(Variable{s, d});
}
Token parse(TokenStream &stream);
Token expression(TokenStream &stream) {
auto lval = stream.get();
auto op = stream.get();
auto rval = stream.get();
double result = 0;
// parens start a sub-expression
if(rval.kind == TokenKind::LPAREN) {
rval = expression(stream);
// eat the RPAREN
auto rparen = stream.get();
if(rparen.kind != TokenKind::RPAREN) {
throw Error{"expected RPAREN"};
}
} else if(rval.kind == TokenKind::NAME) {
auto var_val = get_value(rval.name);
rval = Token{TokenKind::NUMBER, var_val};
}
switch(op.kind) {
case TokenKind::PLUS:
result = lval.value + rval.value;
break;
case TokenKind::MINUS:
result = lval.value - rval.value;
break;
case TokenKind::DIV:
result = lval.value / rval.value;
break;
case TokenKind::MULT:
result = lval.value * rval.value;
default:
throw Error{"invalid syntax in expresion"};
}
return Token{TokenKind::NUMBER, result};
}
Token parse(TokenStream &stream) {
auto tok = stream.get();
switch(tok.kind) {
case TokenKind::NUMBER:
stream.putback(tok);
return expression(stream);
case TokenKind::NAME: {
auto eq = stream.get();
if(eq.kind == TokenKind::EQ) {
auto val = stream.get();
if(val.kind != TokenKind::NUMBER) throw Error{"invalid value in variable assign"};
set_value(tok.name, val.value);
return val;
} else {
auto var_val = get_value(tok.name);
stream.putback(Token{TokenKind::NUMBER, var_val});
stream.putback(eq); // push the next token back on
return expression(stream);
}
}
case TokenKind::LPAREN: {
auto val = expression(stream);
tok = stream.get();
if(tok.kind != TokenKind::RPAREN) {
throw Error{"missing rparen"};
}
return val;
}
case TokenKind::QUIT:
return tok;
default:
cout << "Got token " << ":" << tok.value << " expected NUMBER or LPAREN\n";
throw Error{"invalid syntax in parse"};
}
return Token{TokenKind::ERROR};
}
int main() {
TokenStream stream;
while(cin) {
try {
auto val = parse(stream);
if(val.kind == TokenKind::ERROR) {
cout << "Parse returned an error token.\n";
} else if(val.kind == TokenKind::QUIT) {
cout << "Goodbye!\n";
return 0;
} else {
cout << "=" << val.value << "\n";
}
} catch (Error &err) {
cout << "ERROR " << err.message << "\n";
}
}
return 0;
}