So I may aswell explain while it's relatively fresh in my head(been about 7-8 months since I read Bjarnes book) but I glanced over the code I wrote back then, I'll break it down for you.
So obviously you know multiplication and division will be done before addition and subtraction but the compiler does not know that this rule holds,so you have to come up with a way to make sure the the right order of operations are carried out or else your answer will be wrong but again the compiler doesn't know this. So you need to implement a grammar,a grammar is just a set of rules for example there is grammars in languages -
https://www.skillsyouneed.com/write/grammar1.html, I'm not an English professor so I won't explain it in detail but read that link.
Now you want to implement your own grammar,you want to tell the compiler to multiply and divide before you subtract and add. This is quite tricky but we can accomplish this using while loops and recursion but first we need a way for our grammar to be parsed,so lets break our input into tokens(Tokenizing). Our token will have a type and a number,if the type is a sign such as '+' we will assign it the number 0(this number isn't going to be used anyway) if we have a sign we will assign a fixed random number to it lets use '8', why 8? no reason at all we could have picked any number but Bjarne seemed to like 8. This Token class is fairly simple
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class Token
{
public:
double number;
char type;
Token()
{
type = 'f';
number = 0;
}
Token(int number,char type)
: number(number),type(type)
{
}
Token(char t)
{
type = t;
number = 0;
}
};
|
Next we need a way of storing our tokens,for this we will create a token stream class this token stream class will behave a lot like cin( but we won't be overloading the insertion or puts operator << >> , for simplicity ). This token stream class will also be like a wrapper of cin,and the class will be mostly using the functionality of cin except we will be converting to tokens depending on what input we get from cin. Just like cin we will have a putback function,that will put a token back into our stream if it was not used,we will need a buffer to hold this and also a boolean value to represent if the buffer is full or if it's empty.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
class TokenStream
{
public:
Token buffer;
bool bufferFull;
TokenStream()
{
buffer = NULL;
bufferFull = false;
}
Token getToken()
{
if(bufferFull)
{
bufferFull = false;
return buffer;
}
char value;
cin >> value;
switch(value)
{
case '+':
case '*':
case '/':
case 'q':
{
Token t(value);
return t;
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
cin.putback(value);
double num;
cin >> num;
Token t(num,'8');
return t;
}
}
}
void putBack(Token t){
if(bufferFull){
return;
}
bufferFull = true;
buffer = t;
}
};
|
Now that we have our Token and Token stream class ready to go all we need to do is set up our grammar which is by far the hardest part of the problem.Lets break this down into terms and expressions and primaries,an expression is for example 5 + 5 * 2 or 6 - 2,a term is 5*2 or 5(2),-2, and a primary in this case will just return the number we want to work with(note Bjarne has a much better explanation in his book) so what is our grammar? well a term is made up of primaries lets say 5 * 2 or 5(2),the primaries being 5 and 2 and an expression is made up of terms so 5 + 5 * 2, ie we want the terms to be evaluated before the expression. We make use of iteration and and recursion,(recursion is just a function calling itself)
Ok so lets start with the expression function first
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
double expression(){
double left = term();
Token t = ts.getToken();
while(true){
switch(t.type){
case '+':
left+= term();
t = ts.getToken();
break;
case '-':
left-= term();
t = ts.getToken();
break;
default:
ts.putBack(t);
return left;
}
}
}
|
our value on the left is a term,which is a primary(which will be shown in the term function) so lets call term() and set it's value to whatever term returns.I will come back to the rest of the code,but now we jump into the term() function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
double term(){
double left = primary();
Token t = ts.getToken();
while(true){
switch(t.type){
case '*':
left *= primary();
t = ts.getToken();
break;
case '/':
left /= primary();
t = ts.getToken();
break;
default:
ts.putBack(t);
return left;
}
}
}
|
the left value of our term will be a primary,so we call primary which returns a number,
1 2 3 4 5 6 7 8 9 10 11 12 13
|
double primary(){
Token t = ts.getToken();
switch(t.type){
case '8':
return t.number;
default:
ts.putBack(t);
return 0;
}
}
|
we create a Token named t and use our token stream getToken() function to get a token if done right this should return a number we then check this in a switch if t.type equals '8' we know it's a digit so we return t.number.
Now we come back to our term() function, left is now equals the number returned from primary,we then create another Token t and get a token from our token stream,this token could be a '+' or a '*' but it should be a sign,so now we enter a while loop and evaluate the sign if it's a '*' we call primary and *= it to value of left,if it's not a '*' or '/' we break from the while loop and put back our token to this token can be used by calling expression function. Lets stick with it being a '*' so now after we have done the *= on left we get another Token from the stream and set it equal to t,again this token should be a sign,if a '*' comes up again we repeat the step above again and again until we get a character that is not a '*' or '/'.
lets say we have no more '*'s now we return the left from this function,we now return back to expression and left is equal to the number returned from term(),now get the next Token and enter the while loop in expression(), we check if the sign is a '+' or a '-',so we call term again if there is no more multiplication or division to be done term returns a a number which is returned from primary() now this number is +='d or -='d to left and left is returned
we also need to have a quit condition so we will check to see if it's met in our calculate function,which calls expression,result will hold the result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
void calculation(){
Token t;
int result = 0;
while(true){
cout << "enter expression" << endl;
t = ts.getToken();
if(t.type == 'q'){
break;
}
ts.putBack(t);
result = expression();
}
cout << "result :: " << result << endl;
}
int main()
{
calculation();
}
|
This is quite a lot to take in and understand but after you go through the code a couple of times it should start to make sense, again if you want a better explanation read Bjarnes book he spends almost a full chapter explaining this.
if you have any questions I'll try and answer them.
Good luck