A modular C-like compiler built from scratch — from source code to optimized IR and Virtual Machine execution.
Lexer → Parser → AST → Semantic Analysis → TAC → Optimization → CFG → Virtual Machine
Try the MiniC Compiler directly in your browser.
Write MiniC code, compile it, and explore the complete compilation pipeline including Tokens, AST, Semantic Analysis, TAC/IR, Optimized IR, CFG, and Virtual Machine output.
See the MiniC Compiler in action, including the interactive web interface, compilation stages, optimization, program execution, and error handling.
minic-ui-clean.mp4
The demo showcases the Streamlit-based Compiler Explorer and demonstrates how MiniC processes source code through its compilation pipeline.
The project includes an interactive Streamlit Compiler Explorer for visualizing the complete compilation pipeline.
| Resource | Link |
|---|---|
| 🚀 Live Compiler | Open Streamlit App |
| 💻 Source Code | GitHub Repository |
| 📚 Technical Documentation | Compiler Construction Guide |
MiniC is a compiler implementation for a small C-like programming language, built from scratch in C to demonstrate the core architecture and engineering principles behind modern compilers.
Unlike a simple parser or interpreter, MiniC implements a complete compilation pipeline:
┌────────────────────┐
│ MiniC Source │
│ (.mc) │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ LEXER │
│ Source → Tokens │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ PARSER │
│ Tokens → AST │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ SEMANTIC ANALYSIS │
│ Symbol / Validity │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ TAC / IR │
│ AST → Intermediate │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ OPTIMIZER │
│ Folding + DCE │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ CFG │
│ Blocks + Edges │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ VIRTUAL MACHINE │
│ IR Execution │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ OUTPUT │
└────────────────────┘
The project also includes an interactive Streamlit compiler explorer, allowing users to inspect the output of each compiler stage directly in the browser.
MiniC was designed around a simple principle:
Don't hide the compiler. Expose it.
A source program can be inspected at every major transformation:
Source
↓
Tokens
↓
AST
↓
Semantic Information
↓
Three-Address Code
↓
Optimized Three-Address Code
↓
Control Flow Graph
↓
Virtual Machine Execution
This makes MiniC both a compiler implementation and an interactive environment for understanding compiler internals.
|
|
|
|
|
|
The implementation is intentionally modular.
| Component | Responsibility |
|---|---|
lexer.c |
Source code → tokens |
parser.c |
Tokens → AST |
ast.c |
AST representation and utilities |
semantic.c |
Semantic validation and symbol checking |
codegen.c |
AST → Three-Address Code |
optimizer.c |
IR optimization |
cfg.c |
Basic blocks and CFG construction |
ops.c |
Shared operator semantics |
vm.c |
Optimized IR execution |
main.c |
Compiler pipeline orchestration |
app.py |
Interactive Streamlit interface |
This separation keeps compiler stages independent and makes the system easier to understand and extend.
The lexer converts raw MiniC source code into a stream of tokens.
It recognizes:
- Keywords
- Identifiers
- Integer literals
- Arithmetic operators
- Relational operators
- Logical operators
- Punctuation
- End-of-file
Example:
int x = 10;Conceptually becomes:
INT
IDENT(x)
ASSIGN
NUM(10)
SEMICOLON
Token line information is retained so later compiler stages can provide useful diagnostics.
MiniC uses a recursive-descent parser to transform tokens into an Abstract Syntax Tree.
The AST represents constructs such as:
N_NUM
N_VAR
N_BINOP
N_UNOP
N_ASSIGN
N_DECL
N_IF
N_WHILE
N_FOR
N_PRINT
N_BLOCK
N_PROGRAM
Operator precedence is handled during expression parsing.
For:
x + y * 2the AST represents:
+
/ \
x *
/ \
y 2
rather than incorrectly evaluating:
(x + y) * 2
Parsing determines whether a program has the correct structure.
Semantic analysis determines whether that structurally valid program actually makes sense.
The semantic analyzer performs checks including:
- Declaration validation
- Duplicate declaration detection
- Use-before-declaration detection
- Expression validation
- Statement validation
A symbol table tracks declared variables and their associated information.
After semantic validation, the AST is lowered into Three-Address Code (TAC).
TAC breaks complex expressions into simple operations involving temporary values.
For example:
int result = a + b * 2;can be represented conceptually as:
t1 = 2
t2 = b * t1
t3 = a + t2
result = t3
This creates a clean boundary between the high-level language and the execution engine.
MiniC currently implements two important optimization techniques.
Compile-time evaluation of expressions whose operands are known constants.
2 + 3 * 4
becomes:
14
before runtime.
Computations whose results are never used can be removed from the IR.
For example:
int unused = 100 + 200;
int x = 10;
print(x);does not need to retain the computation for unused.
The optimized IR is divided into basic blocks.
A basic block has:
- A single entry point
- Sequential execution
- A controlled exit
MiniC identifies leaders such as:
- The first instruction
- Jump targets
- Instructions immediately following jumps
These blocks are then connected to form a Control Flow Graph.
Example:
┌──────────────┐
│ Entry │
└──────┬───────┘
│
condition
/ \
▼ ▼
┌────────────┐ ┌────────────┐
│ THEN │ │ ELSE │
└─────┬──────┘ └──────┬─────┘
│ │
└───────┬────────┘
▼
┌──────────────┐
│ EXIT │
└──────────────┘
This exposes the program's control structure in a form commonly used for compiler analysis and optimization.
MiniC does not generate native machine code.
Instead, the optimized TAC is executed by a custom Virtual Machine.
The VM maintains:
Program Counter
Temporary Registers
Variables
Labels
Runtime State
It interprets operations such as:
LOADCONST
LOADVAR
STOREVAR
BINOP
UNOP
PRINT
LABEL
GOTO
IFFALSE_GOTO
This creates a clean backend for experimenting with generated IR without requiring a native machine-code generator.
Operator behavior is centralized in:
ops.c
ops.h
and reused by both the optimizer and the virtual machine.
┌──────────────┐
│ Operator │
│ Engine │
└──────┬───────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Optimizer │ │ VM │
│ Compile-time │ │ Runtime │
└──────────────┘ └──────────────┘
This avoids maintaining separate implementations of arithmetic and logical semantics across compiler stages.
The project includes a Streamlit-based web interface for interacting with the compiler.
Run:
streamlit run app.pyThe interface allows users to write MiniC code and inspect the compiler pipeline.
| View | Information |
|---|---|
| 🔤 Tokens | Lexer output |
| 🌳 AST | Parsed syntax tree |
| 🧠 Semantic | Semantic analysis |
| ⚙️ IR | Generated TAC |
| 🚀 Optimizer | Optimization statistics |
| 📉 Optimized IR | Optimized TAC |
| 🕸️ CFG | Basic blocks and edges |
| ✅ Output | Program execution |
MiniC intentionally implements a compact C-like syntax.
intint x = 10;
x = x + 1;+
-
*
/
<
>
<=
>=
==
!=
if (x > 10) {
print(x);
} else {
print(0);
}while (x < 10) {
x = x + 1;
}for (int i = 0; i < 5; i = i + 1) {
print(i);
}print(x);int a = 10;
int b = 20;
print(a + b);./minicompiler tests/mytest.mc===TOKENS===
===AST===
===SEMANTIC===
===IR===
===OPTIMIZER===
===OPTIMIZED_IR===
===CFG===
===OUTPUT===
30
===END===
The compiler therefore exposes the complete transformation instead of returning only the final result.
# 📁 Repository Structure
```text
MiniC-Compiler/
│
├── lexer.c
├── lexer.h
├── token.h
│
├── parser.c
├── parser.h
│
├── ast.c
├── ast.h
│
├── semantic.c
├── semantic.h
│
├── codegen.c
├── codegen.h
│
├── optimizer.c
├── optimizer.h
│
├── cfg.c
├── cfg.h
│
├── ops.c
├── ops.h
│
├── vm.c
├── vm.h
│
├── main.c
│
├── app.py
├── requirements.txt
│
├── docs/
│ ├── 01-home.png
│ ├── 02-tokens.png
│ ├── 03-ast.png
│ ├── 04-semantic.png
│ ├── 05-ir.png
│ ├── 06-optimizer.png
│ ├── 07-optimized-ir.png
│ ├── 08-cfg.png
│ └── 09-output.png
│
├── tests/
│ ├── test1.mc
│ └── test2_optimizer.mc
│
├── Compiler_Construction_Guide.md
│
├── LICENSE
│
├── README.md
│
└── minicompiler- GCC
- Python 3
- pip
- Streamlit
git clone https://github.com/N230881/MiniC-Compiler.git
cd MiniC-Compilergcc -Wall -o minicompiler *.cOn Windows/MinGW:
gcc -Wall -o minicompiler.exe *.cLinux/macOS:
./minicompiler tests/test1.mcWindows:
minicompiler.exe tests\test1.mc
Install the required dependencies:
pip install -r requirements.txtStart the Streamlit application:
streamlit run app.pyThen open:
http://localhost:8501
The repository includes test programs designed to exercise different compiler stages.
Exercises:
- Variable declarations
- Initialization
- Arithmetic
- Operator precedence
- Conditional statements
- Loops
- Printing
Exercises:
- Constant folding
- Dead code elimination
- Optimized IR
- Conditional execution
- VM execution
- Optimizer statistics
Run:
./minicompiler tests/test1.mc
./minicompiler tests/test2_optimizer.mcMiniC demonstrates practical implementation of:
✓ Lexical Analysis
✓ Recursive-Descent Parsing
✓ Abstract Syntax Trees
✓ Symbol Tables
✓ Semantic Analysis
✓ Three-Address Code
✓ Temporary / Label Generation
✓ Constant Folding
✓ Dead Code Elimination
✓ Basic Blocks
✓ Control Flow Graphs
✓ Custom Virtual Machine
✓ Runtime Interpretation
✓ Structured Diagnostics
✓ Streamlit Visualization
Each compiler stage has a focused responsibility.
TAC provides a simple and inspectable bridge between the AST and execution.
Operator behavior is centralized instead of duplicated.
Every major compiler transformation can be inspected.
The modular architecture makes future additions—new language constructs, optimizations, or backends—easier to implement.
MiniC is intentionally an educational compiler implementation, not a production C compiler.
It currently focuses on:
- Integer-based computation
- Compact C-like syntax
- Compiler pipeline fundamentals
- IR generation
- Basic optimization
- CFG construction
- VM execution
It does not currently provide native machine-code generation, LLVM lowering, or the complete C language.
The goal is to make compiler internals understandable, executable, and inspectable rather than reproduce GCC or Clang.
- Additional primitive types
- Arrays
- Functions
- Return statements
- Nested scopes
- Strings
- Expanded standard library
- Constant propagation
- Copy propagation
- Common subexpression elimination
- Unreachable-code elimination
- Strength reduction
- Loop optimizations
- SSA-based optimization
- LLVM IR generation
- RISC-V backend
- x86-64 backend
- WebAssembly backend
- Graphical AST viewer
- Interactive CFG visualization
- IR before/after comparison
- Syntax highlighting
- Automated regression suite
- Compiler benchmarking
https://github.com/N230881/MiniC-Compiler
See:
Compiler_Construction_Guide.md
for the project's compiler-construction documentation.
Contributions, bug reports, and compiler experiments are welcome.
Create a feature branch:
git checkout -b feature/my-featureBuild:
gcc -Wall -o minicompiler *.cTest:
./minicompiler tests/test1.mc
./minicompiler tests/test2_optimizer.mcThen commit your changes and open a pull request.
This project is licensed under the MIT License.
See the LICENSE file for details.
B.Tech — Artificial Intelligence & Machine Learning
GitHub:
Project:
https://github.com/N230881/MiniC-Compiler








