It isn’t all that bad. Before posting answers for questions like these I almost always write up a little program that actually solves it. I did it in under a hundred lines of code with:
• the 3 functions I recommended to you
• 1 function to get a terminal value (0 or 1 or lookup the value of A, B, ... E)
• 4 functions for the operators, one for each level of precedence: (), AND, OR, XOR
• the 2 “eval” functions I exampled to you above
• main
That’s only eleven functions, and each one is between one and four lines of code long.
Peter87 actually made a mistake: The functions to parse the operators (parentheses, AND, OR, XOR) should each work using a
loop, not an “if”. The last four should be nearly identical, differing only in recognizing the operator symbol and calling the next level of precedence functions. For example:
1 2 3 4 5 6 7
|
int parseXor( std::istream & es, const std::vector<int> & values )
{
int x = parseOr( es, values ); // get the left-hand-side value
while (accept( es, "^" )) // get all right-hand-side values...
x ^= parseOr( es, values ); // ...and apply them to the left-hand-side
return x; // return the final value
}
|
The only function that actually needs the “values” argument is the terminal parsing function (which you might name
parseValue
) so that it can look up what a “B” is, for example.
That way in
main
you can have a loop that just reads the values for A..E and then parse the expression and print the result for as many lines of values you are given for input.
(In a grammar where you have multiple symbols at the same level of precedence the functions get a tiny bit larger. For example, an arithmetic term function would need to handle both addition and subtraction in the same function. Each function exists to handle levels of precedence between operators, and handles all operators with the same level of precedence [and associativity].)
R E C U R S I V E D E S C E N T
The idea behind RD is to take an EBNF-style expression grammar and turn it into code with relatively simple functions. Your grammar is pretty easy:
expression ::= xor
xor ::= or ('^' or)*
or ::= and ('|' and)*
and ::= parentheses ('&' parentheses)*
parentheses ::= '(' expression ')' | value
value ::= '0' | '1' | 'A' | 'B' | 'C' | 'D' | 'E'
The EBNF stuff looks weird, and if you are familiar with regular expressions you will be able to read it easily enough. Things in quotes appear in your input exactly. Anything followed by an asterisk is repeated zero or more times. Things separated by the vertical bar are either-or.
So, a “value” may be the literal symbol
1
, or
B
, etc.
Hopefully you can see how the XOR rule
xor ::= or ('^' or)*
translates directly to a little function:
1 2 3 4 5 6 7
|
int parseXor( std::istream & es, const std::vector<int> & values )
{
int x = parseOr( es, values ); // get the left-hand-side value
while (accept( es, "^" )) // get all right-hand-side values...
x ^= parseOr( es, values ); // ...and apply them to the left-hand-side
return x; // return the final value
}
|
The function is really only complicated because we are carrying some state along as arguments: the current expression stream and the vector of values for 'A'..'F'.
The “terminal” rule/node/function is the one that gets a 0 or 1 value, because it expects there to be a value and nothing else.
On the other end is the top-level “expression” function (which I named
eval
in my examples), which does nothing more than hand-off to the parse function with the lowest precedence. We do this to make modifying the grammar safe and easy.
Oh, yeah, before I forget, since this is a
recursive solution, you will have to provide a forward declaration of the
eval
function so that
parseParentheses
can call it.
RD is one of the fundamental concepts in language parsing. You typically encounter this first in a compiler design course, though... I am not sure I would have made this a 101 assignment.
Anyway, let us know if you have further questions. Try to make it work and make sure to google around Recursive Descent.
Good luck!