0
\$\begingroup\$

I am using "tinyexpr", a cute library that can solve mathematical formulas in a string and I extended it such that it does logical operations with two operands as well. However I wasn't sure how to handle the logical NOT as it is performed on a single operand, which is why I just created a small function that does just that outside of tinyexpr:

void handle_logical_not (char * expr)
{
    int i;

    for(i = 0; expr[i] != '\0'; i++)
    {
        if(expr[i] == '!')
        {
            i++;

            if(expr[i] == '!')
            {
                handle_logical_not(&expr[i]); /* Recursively handle subexpressions */
            }
            if( (expr[i] >= '0' && expr[i] <= '9') || (expr[i] == '-' && (expr[i+1] >= '0' && expr[i+1] <= '9')) )
            {
                long int    val = 0;
                char*       end = NULL;
                size_t      len = 0;

                if(expr[i] == '0')  val = strtol(&expr[i], &end, 8);
                else                val = strtol(&expr[i], &end, 10);

                expr[i-1] = '0' + !val;

                len = (uintptr_t)&expr[MAX_EXPR] - (uintptr_t)end;

                memmove(&expr[i], end, len);
            }
        }
    }
}

You will normally pass a statically allocated string (an expression) to this function and it modifies it directly if it detects a logical NOT operation. For example "!0+!0+!!0" and the function will replace it like that: "1+1+0"

Then you can pass the potentially modified string to tinyexpr for further evaluation. Sadly, this obviously adds an additional complexity overhead.


Remarks and Flaws:

  • As a bonus it adds a case for octal values.
  • It does not skip whitespace characters
\$\endgroup\$
2
  • \$\begingroup\$ What is the expected result of !(1 + 1)? \$\endgroup\$
    – vnp
    May 28, 2022 at 5:32
  • 1
    \$\begingroup\$ @vnp !(1) would not be parsed at all. Parenthesizes are not considered and arithmetic is calculated afterwards in the application I am working on. I actually had a remark about that, but I removed it without consideration. \$\endgroup\$
    – Edenia
    May 28, 2022 at 5:36

1 Answer 1

3
\$\begingroup\$

Declare when needed

// size_t      len = 0;
// ...
// len = (uintptr_t)&expr[MAX_EXPR] - (uintptr_t)end;

size_t len = (uintptr_t)&expr[MAX_EXPR] - (uintptr_t)end;

Scale task to problem size

Consider the string may only be a few bytes, yet MAX_EXPR may be a million. Rather than move lots of bytes, move what is needed.

size_t len = strlen(end) + 1;

(I may have an off-by-one oops here - worthy of more review.)

Note: errno side-effect

Using strtol(), handle_logical_not() may set errno.

+ is a sign too

expr[i] == '-' does not account for + as a sign character.

Octal -123?

if(expr[i] == '0') val = strtol(&expr[i], &end, 8); does not take into account an octal number may be signed. strtol() and "%o" allow negative octal values.

Pedantic: long strings

String array indexing may exceed INT_MAX. Use size_t i.

Nice simply recursion use

Even though a non-recursive solution is possible, this simply handles that issue.

Save time

Use an auto-formatter.

Style

Consider the terser:

            // long int    val = 0;
            long val = 0;

Alternative

Rather than if( (expr[i] >= '0' && expr[i] <= '9') || ..., consider calling strtol(&expr[i], &end, 0) and check for conversion success.

Bonus: accepts white-space, + sign and hexadecimal.

Pedantic: -0

For rare non-2's complement char, use for(i = 0; ((unsigned char *)expr)[i] != '\0'; i++) to not stop on a -0.

Likely irrelevant in next C2x version.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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