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?

\$\endgroup\$
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?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.