CASE STUDY 03 · DATA STRUCTURES

Advanced Two-stack pipeline

Expression Calculator

Convert human-friendly infix expressions into postfix form, evaluate them safely and see how stacks encode precedence, associativity and nested parentheses.

01 · PROBLEM DEFINITION

Computers need the order made explicit

People read 12 + 3 * 4 and know that multiplication happens first. A left-to-right calculation would incorrectly produce 60 instead of 24. Parentheses, precedence and associativity make infix notation comfortable for humans but force a program to remember operators whose execution must wait.

This system uses one stack during conversion and a second stack during evaluation. The conversion phase produces postfix notation, where an operator appears after its operands. Postfix 12 3 4 * + needs no parentheses and can be evaluated with one linear scan.

Parse

Recognise multi-digit integers, operators and parentheses.

Convert

Use an operator stack to create postfix order.

Evaluate

Use a value stack and reject unsafe operations.

02 · EXPRESSION RULES

Define the accepted language before coding

ElementAccepted formExample
OperandsNon-negative, multi-digit integers0, 7, 125
GroupingBalanced round parentheses(12 + 3)
Operators+ − * / % ^8 % 3
WhitespaceOptional between tokens10+2 and 10 + 2
DivisionC integer division7 / 2 = 3
ExcludedUnary minus and decimal operands−5 and 2.5 rejected

Precedence and associativity

OperatorsPrecedenceAssociativity
^3 · highestRight-to-left
* / %2Left-to-right
+ −1 · lowestLeft-to-right
Why associativity matters: 2 ^ 3 ^ 2 means 2 ^ (3 ^ 2), while 20 - 5 - 3 means (20 - 5) - 3.
03 · STACK DESIGN & INVARIANTS

Two stacks solve two different problems

OperatorStack

Stores operators and opening parentheses that cannot yet be emitted. The top holds the next candidate for postfix output.

ValueStack

Stores already evaluated partial results. Every binary operator consumes the top two values and pushes one result.

Operator stack during conversion: [ (, +, * ]
Value stack during evaluation:    [ 12, 20, 3 ]

Conversion invariants

  1. Every processed operand already appears in postfix output exactly once.
  2. Operators still on the stack are waiting for a right operand, a closing parenthesis or a lower-priority incoming operator.
  3. An opening parenthesis is never copied into postfix; it is only a boundary marker.
  4. expectOperand tells whether the next valid token is an operand/( or an operator/).

Evaluation invariant

After processing any valid postfix prefix, the value stack contains exactly the completed subexpression results that have not yet been consumed. A valid full expression finishes with one—and only one—value.

04 · INFIX-TO-POSTFIX ALGORITHM

The shunting-yard decision at each token

Incoming tokenAction
NumberCopy the complete number to postfix
(Push it as a grouping boundary
)Pop to postfix until matching ( is removed
OperatorPop stronger operators; also pop equal left-associative operators; then push incoming operator
EndPop remaining operators; reject any remaining (

The precise pop condition

pop while top is not '(' and
  (precedence(top) > precedence(incoming) or
   same precedence and incoming is left-associative)

For a right-associative incoming ^, an equal ^ remains on the stack. That single exception preserves 2 ^ (3 ^ 2).

Token boundaries

Postfix output separates tokens with spaces. Without delimiters, 12 3 + could be confused with 1 23 +. The parser scans all consecutive digits before emitting a number.

05 · POSTFIX EVALUATION

Operand order is easy to reverse accidentally

  1. When a number appears, push it.
  2. When a binary operator appears, pop right first and then left.
  3. Compute left operator right.
  4. Push the result.
  5. At the end, require exactly one stack value.
Postfix: 20 5 3 - /
Push 20, 5, 3 → '-' pops right=3, left=5 → push 2
'/' pops right=2, left=20 → push 10

Reversing the two pops would calculate 3−5 and then −2/20. Addition hides this bug because it is commutative; subtraction and division expose it.

Runtime checks: the evaluator rejects stack underflow, division by zero, modulo by zero, negative integer exponents and a final stack containing anything other than one result.
06 · COMPLETE IMPLEMENTATION

Compiler-ready C11 program

The program uses fixed-capacity array stacks so the push/pop mechanics remain visible. It validates token order while converting rather than accepting malformed input and hoping evaluation catches it later.

programs/expression-calculator.c
Open Compiler
Loading source…

Important implementation choices

Bounded arrays

Every push checks capacity; postfix appends check the output buffer.

Integer power

Exponentiation by squaring avoids a non-standard dependency and runs in O(log exponent).

Copy before strtok

Evaluation tokenises a copy so the printable postfix expression remains unchanged.

07 · INTERACTIVE PROGRAM TRACING

Trace: 12 + 3 × (8 − 2) ^ 2

  1. The first multi-digit operand is emitted.
  2. Plus waits for its right operand.
  3. The next operand is emitted.
  4. Multiplication waits above addition.
  5. Opening parenthesis creates a boundary.
  6. Tokens inside the group are processed.
  7. The parenthesised expression is closed.
  8. Power waits with the highest precedence.
  9. All remaining operators are emitted.
  10. The value stack produces the final result 120.
Current state

Press Next to begin.

08 · TEST STRATEGY

Test grammar, precedence and runtime errors separately

ExpressionExpected resultPurpose
4242Single operand
12 + 3 * 424Precedence
(12 + 3) * 460Parentheses override
20 - 5 - 312Left associativity
2 ^ 3 ^ 2512Right associativity
7 / 23Integer division
12 / (3 - 3)ErrorDivision by zero
Malformed token order
Inputs 2 3, 2 + * 3, + 4 and 7 - must each produce a useful validation error.
Unbalanced parentheses
Test both (2+3 and 2+3). One leaves an opening marker; the other cannot find a matching opening marker.
Unsupported forms
Inputs -5+2, 2.5+1 and A+3 must be rejected according to the documented grammar.
Capacity boundary
Provide an expression near the configured maximum length and confirm every stack/output overflow path fails safely.
09 · COMPLEXITY & TRADE-OFFS

Every token is pushed and popped at most once

PhaseTimeSpaceReason
Token scanO(n)O(n)Read each character
Infix → postfixO(n)O(n)Each operator pushed/popped once
Postfix evaluationO(n)O(n)Each token processed once
Integer powerO(log e)O(1)Exponentiation by squaring
Complete pipelineO(n + Σlog e)O(n)Expression plus power operations

A recursive-descent parser can evaluate infix directly and produces a clearer syntax tree for richer languages. The two-stack design is ideal here because it exposes stack behaviour, precedence and associativity with a compact implementation.

10 · PRACTICE & EXTENSIONS

Check the decisions behind the algorithm

When evaluating postfix 8 3 -, which value is popped first?

Why does incoming ^ not pop an equal ^?

Build the next version

  1. Add unary minus by distinguishing prefix - from binary subtraction.
  2. Support decimal operands and define division/rounding semantics.
  3. Add variables and a symbol table: total = price * quantity.
  4. Construct an expression tree from postfix and print prefix, infix and postfix traversals.
  5. Detect signed-integer overflow for every arithmetic operation.
11 · INTERVIEW PREPARATION

Explain the algorithm—not only the output

Why is postfix easier to evaluate?

The order of execution is encoded directly in token order. When an operator appears, its operands are already available, so parentheses and precedence checks are unnecessary.

Why are two stacks used?

The operator stack resolves future execution order during conversion. The value stack combines already available operands during evaluation. Their elements and invariants are different.

How do parentheses work?

An opening parenthesis is pushed as a boundary. A closing parenthesis pops operators until that boundary, then removes the opening marker without emitting either parenthesis.

What makes the conversion O(n)?

Although an operator may trigger several pops, each operator is pushed once and popped once across the full scan. The total stack work is therefore linear.

How would you support functions such as max(2,5)?

The tokenizer must recognise names and commas; the conversion algorithm must track function tokens and argument separators; evaluation must know each function’s arity.

12 · KEY TAKEAWAY

A stack stores decisions that cannot be completed yet

The operator stack postpones execution until precedence permits it; the value stack postpones partial results until an operator needs them. By defining the grammar, invariants, error cases and token order explicitly, the project turns a familiar calculator into a rigorous data-structure application.