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
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;
|
|
}
|
|
|