3

How to extend the shunting yard algorithm, that's originally meant for binary operators to support the conditional ternary operator ("a ? b : c") ? I haven't seen an answer to this here and I have one, so I'm posting it.

Yuval
  • 110
  • 6

3 Answers3

3

The way I did it was to add three new operators:

  • "?" ternary-open-if
  • ":" ternary-else
  • ternary-closed-if

Only the first two will be created directly when reading the initial expression.
However, only the third one will exists in the output (the RPN of the initial expression).
The ternary-open-if is put on the operators stack whenever a "?" is seen.
The ternary-else is never put on the stack. Rather, the stack is poped until a ternary-open-if is found, then the ternary-open-if is replaced with the ternary-closed-if (thus indicating that we're in the else part of the conditional operator).
All three operators have higher precedence than all other operators (higher meaning they're evaluated AFTER other operators).
The ternary-if operators have the same precedence and right associativity (like in C), meaning that a ternary-if will never cause a pop of another ternary-if.
The ternary-else has a precedence higher than the ternary-ifs, and its associativity is irrelevant (since it is never put on the stack). So, when encountering a ternary-open-if it will convert it to a closed one as mentioned before.
When encountering a ternary-closed-if it will pop it.

Examples (ternary-closed-if notated as "?:"):

  • "a ? b : c" -->
    "a b c ?:"
  • "a ? b : x ? y : z" -->
    "a b x y z ?: ?:"
  • "a ? x ? y : z : b" -->
    "a x y z ?: b ?:"

This method is more hard to explain than to implement, and it does make a slight change to the algorithm, so if anyone has a simpler solution, please post it.

Yuval
  • 110
  • 6
1

For anyone still searching here, the conditional ternary operator can also be implemented as an IF function with three arguments:

IF([boolean], [expr_if_true], [expr_if_false])

For example, to convert IF(5 > 4, 9, 8) to Reverse Polish Notation by extending the shunting yard algorithm, do:

+-------+---------------------------------------------------+--------------+----------------+
| Token | Action                                            | RPN Output   | Operator stack |
+-------+---------------------------------------------------+--------------+----------------+
| IF    | Push token to stack                               |              | IF             |
| (     | Push token to stack                               |              | IF (           |
| 5     | Add token to output                               | 5            | IF (           |
| >     | Push token to stack                               | 5            | 15 ( >         |
| 4     | Add token to output                               | 5 4          | IF ( >         |
| ,     | Pop from stack onto output until left parenthesis | 5 4 >        | IF (           |
| 9     | Add token to output                               | 5 4 > 9      | IF (           |
| ,     | Pop from stack onto output until left parenthesis | 5 4 > 9      | IF (           |
| 8     | Add token to output                               | 5 4 > 9 8    | IF (           |
| )     | Pop from stack onto output until left parenthesis | 5 4 > 9 8    | IF             |
| end   | Pop entire stack onto output                      | 5 4 > 9 8 IF |                |
+-------+---------------------------------------------------+--------------+----------------+

The postfix is evaluated as:

+-------+------------------------------------------------------------------------------------------+-------+
| Token | Action                                                                                   | Stack |
+-------+------------------------------------------------------------------------------------------+-------+
| 5     | Push to stack                                                                            | 5     |
| 4     | Push to stack                                                                            | 4     |
| >     | Pop from stack twice (5, 4), evaluate (5 > 4) and push onto stack                        | TRUE  |
| 9     | Push onto stack                                                                          | 9     |
| 8     | Push onto stack                                                                          | 8     |
| IF    | Pop from stack thrice (TRUE, 9, 8), evaluate (IF TRUE THEN 9 ELSE 8) and push onto stack | 9     |
+-------+------------------------------------------------------------------------------------------+-------+
Chidi Williams
  • 399
  • 6
  • 16
0

Recursively call shunting yard function after encountering : token, like

token_array shunting_yard(token_stream input)
{
    // assuming its working shunting yard algorithm that works correctly
    // on all unary/binary operators and know how to deal with parenthesis
    if(input.peek().token_type == ?)
    {
        operators_stack.push(?);
        input.seek(til next token);
    }
    // whatever was between ? and : was parsed somewhere above
    if(input.peek().token_type == :)
    {
        input.seek(til next token);
        // recursively call this function
        token_array ret_arr = shunting_yard(input);
        operands_stack = merge two stacks together(operators_stack, ret_array);
        operands_stack.push(operators_stack.pop()) <- should move ?
        input.seek(til next token);
    }
    // important bit
    if(unpaired ')' detected)
    {
        while(operators_stack.size > 0)
            operands_stack.push(operators_stack.pop());
        return operands_stack;
    }
}

Argumentation:

At the example of expression a+b-c?d:e+f and a+b-(c?d:e)+f

case a+b-c?d:e+f
Its bugged, since ternary operator compute it as (a+b-c)?(d):(e+f), which is most definitely not what was intended, but it still a valid ternary expression. gcc, node, firefox, chrome, edge and .net evaluate it like this at least. Who am I to defy computational power of edge console???
Shunting yard, when reached : token should have operands and operators stacks looks like
operands a, b, +, c, -, d
operators ?
Recursive call of shunting_yard returns array e, f, +
After concatenation of returned array to operands stack
operands a, b, +, c, -, d, e, f, +
operators ?
and in the end
stack a, b, +, c, -, d, e, f, +, ?
Evaluating it to check is it returns the same result

a , b , + == a+b, assign result to variable called o1, for short  
o1, c, - == o1 - c == (a + b) - c, assign result to o2  
o2, d, e, f, + == (a + b - c), d, (e + f), assign result to o3  
o2, d, o3, ?, which evaluates as o2?d:o3, which, after variables substitution is the original expression a + b - c ? d : e + f (first operand is condition, second is 'on true' returns, third is 'on false' returns, same below)  

case a+b-(c?d:e)+f
Shunting yard, when reached : token should have operands and operators stacks looks like
operands a, b, +, c, d
operators -, ?
Recursive call of shunting_yard returns array (from a single element) e
After concatenation of returned array to operands stack
operands a, b, +, c, d, e
operators -, ?
Now move ?
operands a, b, +, c, d, e, ?
operators -
Continue parsing as usual, get
stack a, b, +, c, d, e, ? -, f, +
Evaluating

a , b , + == a+b, assign result to o1  
o1, c, d, e, ? == o1, (c?d:e), assign result of ternary operation to o2  
o1, o2, - == o1 - o2 == a + b - (d or e, depending of value of c), assign result to o3  
o3, f, + == o3 + f which after variables substitution is the original expression is the original expression a + b - (d or e, depending of value of c) + f

Other operators works in the same way, if they (operators) have same precendence as described here
https://en.cppreference.com/w/cpp/language/operator_precedence
Nested ternary, while definitely is a bad design, also supported.