Capstone Project 1

Advanced Calculator

Build a powerful scientific calculator in C that parses mathematical expressions, handles operator precedence, supports functions like sin, cos, sqrt, and includes robust error handling. Master stack-based algorithms, tokenization, and expression evaluation.

8-12 hours
Intermediate
400 Points
What You Will Build
  • Expression tokenizer and lexer
  • Infix to postfix converter
  • Stack-based expression evaluator
  • Scientific function library
  • Interactive command-line interface
Contents
01

Project Overview

In this capstone project, you will build a fully functional scientific calculator in C that can parse and evaluate complex mathematical expressions. Unlike simple calculators that operate on two numbers at a time, your calculator will handle expressions like 3 + 4 * 2 / (1 - 5) ^ 2 with proper operator precedence and parentheses support. This project brings together everything you have learned about pointers, dynamic memory allocation, stacks, and string manipulation.

Skills Applied: This project tests your proficiency in C fundamentals, pointers, dynamic memory allocation, stacks, string parsing, and modular programming with header files.
Tokenizer

Parse input strings into tokens (numbers, operators, functions)

Stack ADT

Implement operator and operand stacks for evaluation

Evaluator

Convert infix to postfix and compute results

Functions

Support sin, cos, tan, sqrt, log, and more

Learning Objectives

Technical Skills
  • Implement a lexical analyzer (tokenizer) for mathematical expressions
  • Use the Shunting Yard algorithm for infix to postfix conversion
  • Build a generic stack data structure with dynamic memory
  • Handle operator precedence and associativity correctly
  • Integrate standard math library functions
Engineering Skills
  • Design modular code with separate compilation units
  • Write comprehensive error handling and user feedback
  • Create a clean command-line user interface
  • Document code with clear comments and README
  • Test edge cases and validate input thoroughly
Ready to submit? Already completed the project? Submit your work now!
Submit Now
02

Project Scenario

TechCalc Engineering

You have been hired as a Junior Software Developer at TechCalc Engineering, a company that develops embedded systems and scientific instruments. The R&D team needs a lightweight, portable calculator library that can be integrated into various hardware products. The existing solution relies on heavy third-party libraries, and management wants a clean, dependency-free C implementation.

"We need a calculator that can handle complex engineering formulas with proper operator precedence. It should support trigonometric functions for our signal processing work, and it must be efficient enough to run on resource-constrained embedded devices. Can you build us a clean, modular solution?"

Dr. Sarah Chen, Lead Engineer

Technical Requirements from the Team

Expression Parsing
  • Parse expressions with +, -, *, /, ^, % operators
  • Handle parentheses for grouping
  • Support both integer and floating-point numbers
  • Allow negative numbers and unary minus
Scientific Functions
  • Trigonometric: sin, cos, tan, asin, acos, atan
  • Logarithmic: log (base 10), ln (natural), exp
  • Other: sqrt, abs, pow, ceil, floor
  • Constants: PI, E
Error Handling
  • Division by zero detection
  • Invalid expression syntax errors
  • Mismatched parentheses detection
  • Domain errors (sqrt of negative, log of zero)
User Interface
  • Interactive command-line mode
  • History of previous calculations
  • Help command listing all functions
  • Clear and quit commands
Pro Tip: Think modularly! Separate your code into logical units: tokenizer, stack, evaluator, and main interface. This makes testing and debugging much easier.
03

The Dataset

You will use a comprehensive mathematical expressions dataset for testing your calculator. Download the CSV files containing test expressions with expected results to validate your implementation:

Dataset Download

Download the test expression dataset files and starter code. The CSV files contain expressions with expected results for comprehensive testing.

Related Dataset

Explore the Handwritten Math Symbols Dataset from Kaggle - a collection of 82 different mathematical symbols extracted from the CROHME dataset. This dataset contains images of handwritten math operators (+, -, ×, ÷), digits (0-9), variables, and special symbols - useful for understanding mathematical notation and building expression recognition systems.

Dataset Info: 82 symbol classes | 375,000+ images | Source: CROHME dataset | Symbols: Digits, operators, Greek letters, brackets | Format: 45×45 PNG images
Dataset Schema

ColumnTypeDescription
expression_idIntegerUnique identifier (1-5000)
expressionStringMathematical expression (e.g., "3 + 4 * 2")
expected_resultDecimalCorrect evaluation result
difficultyStringBasic, Intermediate, Advanced
categoryStringArithmetic, Parentheses, Mixed
operators_usedStringList of operators (e.g., "+,*")
has_decimalsBooleanContains decimal numbers (0/1)
has_negativesBooleanContains negative numbers (0/1)

ColumnTypeDescription
test_idIntegerUnique identifier (1-3000)
expressionStringExpression with functions (e.g., "sin(PI/2)")
expected_resultDecimalCorrect evaluation result
function_nameStringPrimary function: sin, cos, sqrt, log, etc.
function_categoryStringTrigonometric, Logarithmic, Other
is_nestedBooleanContains nested functions (0/1)
uses_constantsBooleanUses PI or E constants (0/1)

ColumnTypeDescription
error_idIntegerUnique identifier (1-500)
expressionStringInvalid expression (e.g., "5 / 0")
expected_errorStringExpected error type
error_categoryStringDivByZero, Syntax, Domain, Overflow
descriptionStringHuman-readable error description
Dataset Stats: 8,500+ test expressions, 12 functions, 6 operators, 3 difficulty levels
Coverage: Basic arithmetic to advanced nested functions with edge cases
Sample Data Preview

Here is what typical test expressions look like from math_expressions.csv:

IDExpressionExpectedDifficultyCategory
13 + 4 * 211BasicArithmetic
2(3 + 4) * 214BasicParentheses
32 ^ 3 ^ 2512IntermediateArithmetic
4sin(PI / 2)1IntermediateTrigonometric
5sqrt(16) + log(100)6AdvancedMixed
Testing Note: The dataset includes intentional edge cases: very large numbers, very small decimals, expressions with multiple nested parentheses, and all error types. Use these to thoroughly test your implementation.
04

Key Concepts

Before diving into the implementation, understand these fundamental algorithms that power expression evaluation. These concepts are essential for building a robust calculator.

Operator Precedence

Operators have different priorities. Higher precedence operators are evaluated first:

PrecedenceOperatorDescriptionAssociativity
1 (Highest)( )ParenthesesN/A
2Functionssin, cos, sqrt, etc.Right to Left
3^ExponentiationRight to Left
4* / %Multiplication, Division, ModuloLeft to Right
5 (Lowest)+ -Addition, SubtractionLeft to Right
Shunting Yard Algorithm

This algorithm converts infix notation (how we normally write math) to postfix notation (Reverse Polish Notation), which is easier to evaluate using a stack.

Infix (Input)
3 + 4 * 2

Human-readable format

Shunting Yard

Conversion algorithm

Postfix (Output)
3 4 2 * +

Stack-friendly format

// Shunting Yard Algorithm Pseudocode
while (tokens remain) {
    token = next_token();
    
    if (token is number) {
        output_queue.push(token);
    }
    else if (token is function) {
        operator_stack.push(token);
    }
    else if (token is operator o1) {
        while (operator_stack not empty AND
               top is operator o2 AND
               (o2 has greater precedence OR
                (same precedence AND o1 is left-associative))) {
            output_queue.push(operator_stack.pop());
        }
        operator_stack.push(o1);
    }
    else if (token is '(') {
        operator_stack.push(token);
    }
    else if (token is ')') {
        while (operator_stack.top() != '(') {
            output_queue.push(operator_stack.pop());
        }
        operator_stack.pop();  // Discard '('
    }
}
// Pop remaining operators
while (operator_stack not empty) {
    output_queue.push(operator_stack.pop());
}
Postfix Evaluation

Once you have the postfix expression, evaluation is straightforward using a single stack:

// Postfix Evaluation Algorithm
Stack operand_stack;

for each token in postfix_expression {
    if (token is number) {
        operand_stack.push(token);
    }
    else if (token is operator) {
        double b = operand_stack.pop();
        double a = operand_stack.pop();
        double result = apply_operator(token, a, b);
        operand_stack.push(result);
    }
    else if (token is function) {
        double a = operand_stack.pop();
        double result = apply_function(token, a);
        operand_stack.push(result);
    }
}

return operand_stack.pop();  // Final result
Further Reading: Review the Shunting Yard algorithm on Wikipedia for a detailed explanation with visual examples.
Project Structure
calculator/
├── include/
│   ├── stack.h         # Stack data structure header
│   ├── tokenizer.h     # Tokenizer/Lexer header
│   ├── evaluator.h     # Expression evaluator header
│   └── calculator.h    # Main calculator interface
├── src/
│   ├── stack.c         # Stack implementation
│   ├── tokenizer.c     # Tokenizer implementation
│   ├── evaluator.c     # Evaluator implementation
│   └── main.c          # Entry point and CLI
├── tests/
│   └── test_calc.c     # Unit tests
├── Makefile            # Build configuration
└── README.md           # Project documentation
Your Task: Implement the functions in stack.c, tokenizer.c, and evaluator.c. The starter code includes complete header files with all required function signatures. Download the starter files from the Dataset section above.
05

Project Requirements

Complete all components below to build a fully functional calculator. Each step builds upon the previous one, taking you from basic tokenization to a complete interactive calculator with scientific functions.

1
Stack Data Structure

Implementation Requirements:

  • Implement all stack operations in stack.c
  • stack_init() - Initialize empty stack with top = -1
  • stack_push() - Add token, check for overflow, return success status
  • stack_pop() - Remove and return top token, handle underflow
  • stack_peek() - Return top token without removing
  • stack_is_empty() and stack_is_full() - Status checks

Token Structure:

  • Support 5 token types: NUMBER, OPERATOR, FUNCTION, LPAREN, RPAREN
  • Use union to store: double number, char operator, char function[16]
  • Set STACK_MAX_SIZE to 256 elements
Deliverable: Fully implemented stack.c with all 6 functions working correctly. Test with sample tokens before proceeding.
2
Tokenizer (Lexical Analysis)

Core Tokenization:

  • tokenize() - Convert input string to TokenList array
  • Parse numbers: integers, decimals, scientific notation (1.5e-3)
  • Identify operators: +, -, *, /, ^, %, (, )
  • Detect functions: sin, cos, tan, sqrt, log, ln, exp, abs, etc.
  • Handle whitespace properly (skip spaces, tabs)
  • Support constants: replace PI with 3.14159265359, E with 2.71828182846

Helper Functions:

  • is_operator(char c) - Check if character is valid operator
  • is_function(const char *name) - Validate function names
  • get_precedence(char op) - Return operator precedence (1-4)
  • is_left_associative(char op) - Check associativity (^ is right-associative)

Precedence Levels:

  • Level 1: + and - (addition, subtraction)
  • Level 2: * / % (multiplication, division, modulo)
  • Level 3: ^ (exponentiation, right-associative)
  • Level 4: Functions and unary operators
Deliverable: Complete tokenizer.c that converts "3 + sin(PI/2) * 4" into a valid TokenList with proper types.
3
Infix to Postfix Converter

Shunting Yard Algorithm:

  • infix_to_postfix() - Implement Dijkstra's algorithm
  • Process tokens left-to-right from infix TokenList
  • Numbers go directly to output queue
  • Operators pushed to stack based on precedence
  • Functions always pushed to stack
  • Left parenthesis pushed to stack
  • Right parenthesis pops until matching left parenthesis found

Algorithm Rules:

  • If operator has lower/equal precedence and left-associative, pop stack first
  • Right-associative operators (^) pop only if next has strictly higher precedence
  • Functions have highest precedence
  • After all tokens processed, pop remaining operators to output

Example Conversions:

Infix:    3 + 4 * 2 / (1 - 5) ^ 2
Postfix:  3 4 2 * 1 5 - 2 ^ / +

Infix:    sin(PI / 2) + sqrt(16)
Postfix:  PI 2 / sin 16 sqrt +
Deliverable: Working infix_to_postfix() function that correctly converts complex expressions. Must handle mismatched parentheses errors.
4
Postfix Expression Evaluator

Evaluation Algorithm:

  • evaluate_postfix() - Calculate result from postfix TokenList
  • Use a stack to store intermediate results (double values)
  • Process postfix tokens left-to-right
  • Numbers pushed to result stack
  • Operators pop 2 operands, compute, push result
  • Functions pop 1 operand, compute, push result

Scientific Functions (Use math.h):

Trigonometric
  • sin(x), cos(x), tan(x)
  • asin(x), acos(x), atan(x)
Logarithmic
  • log(x) - log10(x)
  • ln(x) - log(x)
  • exp(x) - pow(E, x)
Other
  • sqrt(x), abs(x)
  • ceil(x), floor(x)
  • pow(x,y) for ^ operator

Error Detection:

  • CALC_ERROR_DIV_ZERO: Division by zero or modulo zero
  • CALC_ERROR_DOMAIN: sqrt(-1), log(0), asin(2)
  • CALC_ERROR_OVERFLOW: Result too large (check isinf())
  • Return status codes instead of crashing
Deliverable: Complete evaluator.c with evaluate_postfix(), calculate() wrapper, and get_error_message() helper.
5
Interactive Command-Line Interface

REPL Implementation:

  • Display prompt > and wait for user input
  • Read expression with fgets(), remove newline character
  • Call calculate() and display result or error
  • Loop until user types quit or exit

Special Commands:

  • help - Display all operators, functions, constants, commands
  • history - Show last 50 calculations with results
  • clear - Clear calculation history
  • quit or exit - Exit program with goodbye message

History Management:

  • Store up to 50 calculations in char history[50][256] array
  • Format: "3 + 4 * 2 = 11"
  • Do not store commands like help or history

Output Formatting:

  • Results: = 42 or = 3.14159265359
  • Use %.10g format to avoid trailing zeros
  • Errors: Error: Division by zero in red if possible
Deliverable: Polished main.c with complete REPL, all commands working, and user-friendly prompts and messages.
6
Build System and Testing

Makefile Targets:

  • make or make all - Build calculator executable
  • make clean - Remove object files and executable
  • make test - Run unit tests (optional bonus)
  • Link with math library: -lm flag
  • Enable warnings: -Wall -Wextra -std=c11

Testing with Dataset:

  • Test against math_expressions.csv - basic arithmetic
  • Test against function_tests.csv - scientific functions
  • Test against error_cases.csv - error handling
  • Compare your results with expected outputs
  • Aim for 95%+ accuracy on provided test cases

Code Quality:

  • No compiler warnings with -Wall -Wextra
  • No memory leaks (run with valgrind if available)
  • Proper include guards in all header files
  • Consistent indentation and naming conventions
  • Comments explaining complex logic (especially Shunting Yard)
Deliverable: Working Makefile, complete project compiles without errors, passes at least 95% of provided test cases, no memory leaks.
Development Tip: Implement and test each component individually before moving to the next. Start with stack, then tokenizer, then conversion, then evaluation. This modular approach makes debugging much easier.
06

Example Usage

Here is what a typical session with your calculator should look like:

$ ./calculator
TechCalc v1.0 - Type 'help' for commands

> 3 + 4 * 2
= 11

> (3 + 4) * 2
= 14

> 2 ^ 3 ^ 2
= 512

> sin(PI / 2)
= 1

> sqrt(16) + log(100)
= 6

> 10 / (5 - 5)
Error: Division by zero

> sqrt(-4)
Error: Domain error - sqrt of negative number

> (3 + 4
Error: Mismatched parentheses

> help

=== TechCalc Advanced Calculator ===
Operators: + - * / ^ % ()
Functions: sin, cos, tan, asin, acos, atan
           sqrt, log, ln, exp, abs, ceil, floor
Constants: PI, E
Commands:  help, history, clear, quit

> history

=== Calculation History ===
1: 3 + 4 * 2 = 11
2: (3 + 4) * 2 = 14
3: 2 ^ 3 ^ 2 = 512
4: sin(PI / 2) = 1
5: sqrt(16) + log(100) = 6

> quit
Goodbye!
Test Cases for Validation
ExpressionExpected ResultTests
3 + 47Basic addition
10 - 3 * 24Operator precedence
(10 - 3) * 214Parentheses
2 ^ 101024Exponentiation
17 % 52Modulo
-5 + 105Unary minus
sin(0)0Trig function
cos(PI)-1Constant + function
sqrt(2 ^ 2 + 3 ^ 2)3.6055...Nested operations
log(1000) / log(10)3Log operations
exp(ln(5))5Inverse functions
ceil(4.2) + floor(4.8)9Rounding functions
07

Submission Requirements

Create a public GitHub repository with the exact name shown below:

Required Repository Name
c-advanced-calculator
github.com/<your-username>/c-advanced-calculator
Required Project Structure
c-advanced-calculator/
├── include/
│   ├── stack.h         # Stack data structure header
│   ├── tokenizer.h     # Tokenizer/Lexer header
│   ├── evaluator.h     # Expression evaluator header
│   └── calculator.h    # Main calculator interface
├── src/
│   ├── stack.c         # Stack implementation
│   ├── tokenizer.c     # Tokenizer implementation
│   ├── evaluator.c     # Evaluator implementation
│   └── main.c          # Entry point and CLI
├── tests/
│   └── test_calc.c     # Unit tests
├── Makefile            # Build configuration
└── README.md           # Project documentation
README.md Required Sections
1. Project Header
  • Project title and description
  • Your full name and submission date
  • Course and project number
2. Features
  • List of supported operators
  • List of supported functions
  • Error handling capabilities
3. Build Instructions
  • Prerequisites (compiler, etc.)
  • How to compile with make
  • How to run the program
4. Usage Examples
  • 5-10 example calculations
  • Screenshots of terminal output
  • Demo of error handling
5. Implementation Details
  • Shunting Yard algorithm explanation
  • Data structures used
  • Code organization overview
6. Testing
  • How to run test cases
  • Test coverage description
  • Edge cases handled
7. Challenges and Solutions
  • Difficulties encountered
  • How you solved them
  • What you learned
8. Contact
  • GitHub profile link
  • LinkedIn (optional)
  • Email (optional)
Do Include
  • All source files (.c) and headers (.h)
  • Working Makefile
  • Test file with at least 20 test cases
  • Screenshots showing functionality
  • Complete README with all sections
  • Clean, commented code
Do Not Include
  • Compiled binaries or object files (*.o, *.exe)
  • IDE-specific files (.vscode/, .idea/)
  • Temporary or backup files
  • Plagiarized code from online sources
  • Broken or incomplete implementations
Important: Before submitting, verify that your code compiles without warnings using gcc -Wall -Wextra and all test cases pass.
Submit Your Project

Enter your GitHub username - we will verify your repository automatically

08

Grading Rubric

Your project will be graded on the following criteria. Total: 400 points.

Criteria Points Description
Core Expression Evaluation 100 Basic operations, precedence, parentheses, negatives
Scientific Functions 75 At least 12 functions implemented correctly
Error Handling 75 All error types detected with helpful messages
User Interface 50 REPL, help, history, clear, quit commands
Code Quality 50 Modular design, comments, no memory leaks
Documentation 50 README quality, screenshots, build instructions
Total 400
Grading Levels
Excellent
360-400

Exceeds all requirements with exceptional quality

Good
300-359

Meets all requirements with good quality

Satisfactory
240-299

Meets minimum requirements

Needs Work
< 240

Missing key requirements

Ready to Submit?

Make sure you have completed all requirements and reviewed the grading rubric above.

Submit Your Project
Grading Levels
Excellent
360-400

Exceeds all requirements with exceptional quality

Good
300-359

Meets all requirements with good quality

Satisfactory
240-299

Meets minimum requirements

Needs Work
< 240

Missing key requirements

Ready to Submit?

Make sure you have completed all requirements and reviewed the grading rubric above.

Submit Your Project
09

Pre-Submission Checklist

Use this checklist to verify you have completed all requirements before submitting your project.

Core Functionality
Scientific Functions
Error Handling
Repository Requirements
Final Check: Compile your code with gcc -Wall -Wextra -Werror to ensure there are no warnings or errors.
10

Common Issues and Solutions

Encountering problems? Here are the most common issues students face and how to resolve them.

Incorrect Operator Precedence
Problem

Expression 3 + 4 * 2 gives 14 instead of 11

Solution

Review your get_precedence() function. Multiplication should have higher precedence than addition. Also check the while loop condition in Shunting Yard algorithm.

Tip: Use print_tokens() to debug token parsing
Parentheses Mismatch Not Detected
Problem

Expression (3 + 4 does not show error, program crashes

Solution

After processing all tokens in infix_to_postfix(), check if any left parentheses remain on the stack. Return CALC_ERROR_MISMATCH_PAREN if found.

Important: Count parentheses during tokenization
sin() and cos() Give Wrong Values
Problem

sin(90) does not equal 1, gives strange values

Solution

C math library functions use radians, not degrees. Use sin(90 * PI / 180) or convert in your function: sin(x * M_PI / 180.0).

Debug: Test with sin(PI/2) which should equal 1.0
Linker Error: Undefined Reference to pow
Problem

Compilation fails with undefined reference to 'sqrt', 'sin', etc.

Solution

Link with the math library by adding -lm flag to your gcc command or Makefile: gcc -o calc main.c -lm

Prevention: Always include -lm in your Makefile LDFLAGS
Stack Overflow or Segmentation Fault
Problem

Program crashes with complex expressions or seg faults

Solution

Check stack_is_full() before pushing and stack_is_empty() before popping. Initialize top = -1 in stack_init(). Verify array bounds.

Alternative: Increase STACK_MAX_SIZE if needed
Exponentiation Right-Associativity Wrong
Problem

2^3^2 gives 64 instead of 512

Solution

Make is_left_associative('^') return false. In Shunting Yard, only pop operators with strictly higher precedence for right-associative operators.

Note: 2^3^2 should equal 2^(3^2) = 2^9 = 512
Still Having Issues?

Check the course discussion forum or reach out for help

Wrong Precedence Results
Problem

3 + 4 * 2 returns 14 instead of 11

Solution

Check your precedence table. Multiplication should have higher precedence than addition. Verify the Shunting Yard comparison logic.

Exponentiation Wrong
Problem

2 ^ 3 ^ 2 returns 64 instead of 512

Solution

Exponentiation is right-associative! 2^3^2 = 2^(3^2) = 2^9 = 512. Update your associativity check.

Unary Minus Not Working
Problem

-5 + 3 causes syntax error or wrong result

Solution

In tokenizer, check if minus follows an operator or is at start. Convert to 0 - 5 or treat as unary operator with high precedence.

Memory Leaks
Problem

Valgrind reports memory leaks after running

Solution

If using dynamic allocation, ensure every malloc has a corresponding free. Consider using stack-allocated arrays with fixed sizes.