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.
 
 
 
 
 

233 lines
4.7 KiB

#include <iostream>
#include <queue>
using namespace std;
class Error {
public:
string message;
Error(string m) : message{m} {};
};
enum TokenKind {
SEMI_COLON,
QUIT,
LPAREN,
RPAREN,
PLUS,
MINUS,
MULT,
DIV,
NUMBER,
NAME,
EQ,
ERROR
};
class Token {
public:
TokenKind kind = 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:
Token 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{SEMI_COLON};
case 'q': return Token{QUIT};
case '(': return Token{LPAREN};
case ')': return Token{RPAREN};
case '+': return Token{PLUS};
case '-': return Token{MINUS};
case '*': return Token{MULT};
case '/': return Token{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);
double val = 0;
cin >> val;
return Token{NUMBER, val};
}
case '=':
return Token{EQ};
default:
if(isalpha(ch)) {
cin.putback(ch);
string s;
cin >> s;
return Token{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) {
Token lval = stream.get();
Token op = stream.get();
Token rval = stream.get();
double result = 0;
// parens start a sub-expression
if(rval.kind == LPAREN) {
rval = expression(stream);
// eat the RPAREN
Token rparen = stream.get();
if(rparen.kind != RPAREN) {
throw Error{"expected RPAREN"};
}
} else if(rval.kind == NAME) {
double var_val = get_value(rval.name);
rval = Token{NUMBER, var_val};
}
switch(op.kind) {
case PLUS:
result = lval.value + rval.value;
break;
case MINUS:
result = lval.value - rval.value;
break;
case DIV:
result = lval.value / rval.value;
break;
case MULT:
result = lval.value * rval.value;
default:
throw Error{"invalid syntax in expresion"};
}
return Token{NUMBER, result};
}
Token parse(TokenStream &stream) {
Token tok = stream.get();
switch(tok.kind) {
case NUMBER:
stream.putback(tok);
return expression(stream);
case NAME: {
Token eq = stream.get();
if(eq.kind == EQ) {
Token val = stream.get();
if(val.kind != NUMBER) throw Error{"invalid value in variable assign"};
set_value(tok.name, val.value);
return val;
} else {
double var_val = get_value(tok.name);
stream.putback(Token{NUMBER, var_val});
stream.putback(eq); // push the next token back on
return expression(stream);
}
}
case LPAREN: {
Token val = expression(stream);
tok = stream.get();
if(tok.kind != RPAREN) {
throw Error{"missing rparen"};
}
return val;
}
case QUIT:
return tok;
default:
cout << "Got token " << tok.kind << ":" << tok.value << " expected NUMBER or LPAREN\n";
throw Error{"invalid syntax in parse"};
}
return Token{ERROR};
}
int main() {
TokenStream stream;
while(cin) {
try {
Token val = parse(stream);
if(val.kind == ERROR) {
cout << "Parse returned an error token.\n";
} else if(val.kind == QUIT) {
cout << "Goodbye!\n";
return 0;
} else {
cout << "=" << val.value << "\n";
}
} catch (Error &err) {
cout << "ERROR " << err.message << "\n";
}
}
return 0;
}