3
\$\begingroup\$

Conditional parsing (aka read some tokens and return true or false).

As a solution to this SO question, I wrote following code:

bool nectar_loader::resolve_conditional( const std::function<bool(const string&)> &config_contains )
{
 bool result = true;
 // My very own recursive implementation
 /*
 - each set of parenthesis is handled recursively
 - logical AND: +
 - logical OR: |
 - logical NOT: !
 - two bools: "result" and "current"
 - "result" keeps global result, and is modified by "+"
 - "current" keeps results for "|" and "!"
 - syntax checking for invalid
 */
 bool previous_was_operator = true;
 bool current = false; // keep track of current state
 string token;
 while( next_token(token) )
 {
 conditional_operator op;
 if( map_value(conditional_operator_map, token, op) )
 {
 if( previous_was_operator )
 {
 if( op == conditional_operator::not_op )
 current ^= current; // negate next
 else
 {
 syntax_error( "Conditional operators \'+\', \'|\', \')\', and \'(\' must be followed by a CONFIG string." );
 break;
 }
 }
 else
 {
 switch(op)
 {
 case conditional_operator::right_parenthesis:
 // TODO: allow conditionals without outer parenthesis of the form (a+b)|(c)
 return result;
 case conditional_operator::left_parenthesis: // recurse
 return resolve_conditional( config_contains );
 case conditional_operator::plus_op:
 result = result && current; // "current" -> "result"
 if( !result ) // negative when an element of a "+" expression
 current = false; // reset "current"
 break;
 case conditional_operator::or_op: // combine with "current"
 case conditional_operator::not_op: // unreachable
 throw runtime_error( "Internal logic error in nectar_loader::resolve_conditional" );
 }
 }
 previous_was_operator = true;
 }
 else if( !previous_was_operator )
 {
 syntax_error( "In a conditional all CONFIG strings must be seperated by a conditional operator \'+\', \'|\', \')\', or \'(\'." );
 break;
 }
 else // previous was operator, so now we have a CONFIG string
 {
 // check CONFIG string, and perform a logical OR
 // TODO: check effect of "!"
 current = current || config_contains(token);
 }
 }
 return result;
}

next_token() reads the next token, and returns false if this fails. syntax_error sets a global error status with message which is checked after calling this function. map_value returns true if the token can be converted to an enum class value (so if it is a conditional operator as defined by me). This algorithm is more flexible than a shunting yard (due to my future plans).

I have not tested this extremely well, but what do you think?

asked Jun 11, 2011 at 15:55
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

A few comments:

bool nectar_loader::resolve_conditional( const std::function<bool(const string&)> &config_contains )

I'd generally prefer to turn this into a template, and pass the correct function type (and if I used std::function at all, use it only internally to store something):

template <class func> 
bool nextar_loader::resolve_conditional(func &config_contains) {
 bool result = true;
 // My very own recursive implementation
 /*
 - each set of parenthesis is handled recursively
 - logical AND: +
 - logical OR: |
 - logical NOT: !
 - two bools: "result" and "current"
 - "result" keeps global result, and is modified by "+"
 - "current" keeps results for "|" and "!"
 - syntax checking for invalid
 */

I'd prefer to move the block comment to just above the function header. I'd also prefer more descriptive names than result and current. Given their use, I'd consider names more like "expression_value" and "subexpression_value".

 bool previous_was_operator = true;
 bool current = false; // keep track of current state

This comment seems to add nothing to our understanding of the code. I'd consider something more like: "// Assume current sub-expression is invalid until parsed." (assuming that's really why you've initialized it to false).

 string token;
 while( next_token(token) )
 {
 conditional_operator op;
 if( map_value(conditional_operator_map, token, op) )
 {
 if( previous_was_operator )
 {
 if( op == conditional_operator::not_op )
 current ^= current; // negate next

Again, the comment seems to do little to assist understanding of the code. I'd probably start by pointing out that an expression like a op1 op2 b is valid if and only if op2 is a unary operator (of which this grammar apparently only supports one).

 case conditional_operator::plus_op:
 result = result && current; // "current" -> "result"

Given that you're using it as an and, I'd name this conditional_operator::and_op (or something similar). Then name should reflect the logical meaning of the operator, not the fact that it happens to be (mis-)represented with the + character.

 if( !result ) // negative when an element of a "+" expression
 current = false; // reset "current"

This if statement appears redundant (at least to me). You've just set result to the logical and of current and result, so it will be true if and only if both inputs were already true.

result = current = (current && result);

So, both end up true if and only if both started out true. If either started out false, both end up false.

 break;
 case conditional_operator::or_op: // combine with "current"
 case conditional_operator::not_op: // unreachable
 throw runtime_error( "Internal logic error in nectar_loader::resolve_conditional" );

This looks rather like a bug. Did you really intend that or_op would throw an exception as unreachable, or should there be a break after the or_op case?

answered Mar 24, 2014 at 4:51
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.