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.
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
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?"
Technical Requirements from the Team
- Parse expressions with +, -, *, /, ^, % operators
- Handle parentheses for grouping
- Support both integer and floating-point numbers
- Allow negative numbers and unary minus
- Trigonometric: sin, cos, tan, asin, acos, atan
- Logarithmic: log (base 10), ln (natural), exp
- Other: sqrt, abs, pow, ceil, floor
- Constants: PI, E
- Division by zero detection
- Invalid expression syntax errors
- Mismatched parentheses detection
- Domain errors (sqrt of negative, log of zero)
- Interactive command-line mode
- History of previous calculations
- Help command listing all functions
- Clear and quit commands
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 Schema
| Column | Type | Description |
|---|---|---|
expression_id | Integer | Unique identifier (1-5000) |
expression | String | Mathematical expression (e.g., "3 + 4 * 2") |
expected_result | Decimal | Correct evaluation result |
difficulty | String | Basic, Intermediate, Advanced |
category | String | Arithmetic, Parentheses, Mixed |
operators_used | String | List of operators (e.g., "+,*") |
has_decimals | Boolean | Contains decimal numbers (0/1) |
has_negatives | Boolean | Contains negative numbers (0/1) |
| Column | Type | Description |
|---|---|---|
test_id | Integer | Unique identifier (1-3000) |
expression | String | Expression with functions (e.g., "sin(PI/2)") |
expected_result | Decimal | Correct evaluation result |
function_name | String | Primary function: sin, cos, sqrt, log, etc. |
function_category | String | Trigonometric, Logarithmic, Other |
is_nested | Boolean | Contains nested functions (0/1) |
uses_constants | Boolean | Uses PI or E constants (0/1) |
| Column | Type | Description |
|---|---|---|
error_id | Integer | Unique identifier (1-500) |
expression | String | Invalid expression (e.g., "5 / 0") |
expected_error | String | Expected error type |
error_category | String | DivByZero, Syntax, Domain, Overflow |
description | String | Human-readable error description |
Sample Data Preview
Here is what typical test expressions look like from math_expressions.csv:
| ID | Expression | Expected | Difficulty | Category |
|---|---|---|---|---|
| 1 | 3 + 4 * 2 | 11 | Basic | Arithmetic |
| 2 | (3 + 4) * 2 | 14 | Basic | Parentheses |
| 3 | 2 ^ 3 ^ 2 | 512 | Intermediate | Arithmetic |
| 4 | sin(PI / 2) | 1 | Intermediate | Trigonometric |
| 5 | sqrt(16) + log(100) | 6 | Advanced | Mixed |
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:
| Precedence | Operator | Description | Associativity |
|---|---|---|---|
| 1 (Highest) | ( ) | Parentheses | N/A |
| 2 | Functions | sin, cos, sqrt, etc. | Right to Left |
| 3 | ^ | Exponentiation | Right to Left |
| 4 | * / % | Multiplication, Division, Modulo | Left to Right |
| 5 (Lowest) | + - | Addition, Subtraction | Left 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
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
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.
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.
Stack Data Structure
Implementation Requirements:
- Implement all stack operations in
stack.c stack_init()- Initialize empty stack with top = -1stack_push()- Add token, check for overflow, return success statusstack_pop()- Remove and return top token, handle underflowstack_peek()- Return top token without removingstack_is_empty()andstack_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_SIZEto 256 elements
stack.c with all 6 functions working correctly.
Test with sample tokens before proceeding.
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 operatoris_function(const char *name)- Validate function namesget_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
tokenizer.c that converts
"3 + sin(PI/2) * 4" into a valid TokenList with proper types.
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 +
infix_to_postfix() function that correctly
converts complex expressions. Must handle mismatched parentheses errors.
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
evaluator.c with evaluate_postfix(),
calculate() wrapper, and get_error_message() helper.
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
quitorexit
Special Commands:
help- Display all operators, functions, constants, commandshistory- Show last 50 calculations with resultsclear- Clear calculation historyquitorexit- 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
helporhistory
Output Formatting:
- Results:
= 42or= 3.14159265359 - Use
%.10gformat to avoid trailing zeros - Errors:
Error: Division by zeroin red if possible
main.c with complete REPL, all commands working,
and user-friendly prompts and messages.
Build System and Testing
Makefile Targets:
makeormake all- Build calculator executablemake clean- Remove object files and executablemake test- Run unit tests (optional bonus)- Link with math library:
-lmflag - 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
valgrindif available) - Proper include guards in all header files
- Consistent indentation and naming conventions
- Comments explaining complex logic (especially Shunting Yard)
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
| Expression | Expected Result | Tests |
|---|---|---|
3 + 4 | 7 | Basic addition |
10 - 3 * 2 | 4 | Operator precedence |
(10 - 3) * 2 | 14 | Parentheses |
2 ^ 10 | 1024 | Exponentiation |
17 % 5 | 2 | Modulo |
-5 + 10 | 5 | Unary minus |
sin(0) | 0 | Trig function |
cos(PI) | -1 | Constant + function |
sqrt(2 ^ 2 + 3 ^ 2) | 3.6055... | Nested operations |
log(1000) / log(10) | 3 | Log operations |
exp(ln(5)) | 5 | Inverse functions |
ceil(4.2) + floor(4.8) | 9 | Rounding functions |
Submission Requirements
Create a public GitHub repository with the exact name shown below:
Required Repository Name
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
gcc -Wall -Wextra and all
test cases pass.
Enter your GitHub username - we will verify your repository automatically
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
Exceeds all requirements with exceptional quality
Good
Meets all requirements with good quality
Satisfactory
Meets minimum requirements
Needs Work
Missing key requirements
Ready to Submit?
Make sure you have completed all requirements and reviewed the grading rubric above.
Submit Your ProjectGrading Levels
Excellent
Exceeds all requirements with exceptional quality
Good
Meets all requirements with good quality
Satisfactory
Meets minimum requirements
Needs Work
Missing key requirements
Ready to Submit?
Make sure you have completed all requirements and reviewed the grading rubric above.
Submit Your ProjectPre-Submission Checklist
Use this checklist to verify you have completed all requirements before submitting your project.
Core Functionality
Scientific Functions
Error Handling
Repository Requirements
gcc -Wall -Wextra -Werror to ensure there are no warnings or errors.
Common Issues and Solutions
Encountering problems? Here are the most common issues students face and how to resolve them.
Incorrect Operator Precedence
Expression 3 + 4 * 2 gives 14 instead of 11
Review your get_precedence() function. Multiplication should have higher precedence than addition. Also check the while loop condition in Shunting Yard algorithm.
print_tokens() to debug token parsing
Parentheses Mismatch Not Detected
Expression (3 + 4 does not show error, program crashes
After processing all tokens in infix_to_postfix(), check if any left parentheses remain on the stack. Return CALC_ERROR_MISMATCH_PAREN if found.
sin() and cos() Give Wrong Values
sin(90) does not equal 1, gives strange values
C math library functions use radians, not degrees. Use sin(90 * PI / 180) or convert in your function: sin(x * M_PI / 180.0).
sin(PI/2) which should equal 1.0
Linker Error: Undefined Reference to pow
Compilation fails with undefined reference to 'sqrt', 'sin', etc.
Link with the math library by adding -lm flag to your gcc command or Makefile: gcc -o calc main.c -lm
-lm in your Makefile LDFLAGS
Stack Overflow or Segmentation Fault
Program crashes with complex expressions or seg faults
Check stack_is_full() before pushing and stack_is_empty() before popping. Initialize top = -1 in stack_init(). Verify array bounds.
Exponentiation Right-Associativity Wrong
2^3^2 gives 64 instead of 512
Make is_left_associative('^') return false. In Shunting Yard, only pop operators with strictly higher precedence for right-associative operators.
2^3^2 should equal 2^(3^2) = 2^9 = 512
Still Having Issues?
Check the course discussion forum or reach out for help