University of Central Punjab (UCP) Program: Bachelor of Science in Computer Science (BSCS) Semester: 7th Semester — Fall 2024 Course: Compiler Construction Instructor: Ma'am Arooj Zahra
Ali Zaman
- 📧 Email: alizaman6780@gmail.com
- 🔗 LinkedIn: linkedin.com/in/ali-zaman-web-developer
- 🐙 GitHub: github.com/MyProVerse
- 📦 Repository: Compiler-Construction-BSCS-F24-Batch-Project-UCP
This project is a two-phase implementation of a Compiler Front-End built as part of the Compiler Construction course in the 7th semester. The project covers the first two stages of compiler design: Lexical Analysis (Phase 1) and Syntax Analysis (Phase 2). It targets a custom language that extends standard C++ constructs with Urdu-inspired keywords (agar, magar, loop), designed as an educational exploration of compiler design principles.
The project is implemented entirely in C++ and processes plain text source code files, producing structured token streams and parse trees as output.
Compiler-Construction-BSCS-F24-Batch-Project-UCP/
│
├── Phase1/
│ ├── code.cpp # Lexical analyzer (scanner) source
│ ├── sourcecode.txt # Input: source code to be analyzed
│ ├── Token.txt # Output: classified tokens with line numbers
│ ├── Error.txt # Output: lexical error log
│ └── RE and FA.pdf # Documentation: Regular Expressions & Finite Automata
│
├── phase2/
│ ├── grammar, first and follow and LL1 table/
│ │ ├── first and follow.txt # First and Follow sets documentation
│ │ ├── LL1.docx # LL(1) Parsing Table (Word document)
│ │ └── revised grammar.txt # Grammar rules used by the parser
│ ├── scanner.cpp # Updated scanner (lexer) for Phase 2
│ ├── main.cpp # LL(1) recursive descent parser
│ ├── sourcecode.txt # Input: source code to be parsed
│ ├── Token.txt # Output: token stream from scanner
│ ├── TokenError.txt # Output: lexical error log (Phase 2)
│ ├── Error.txt # Output: syntax/parse error log
│ └── parse_tree.txt # Output: parse tree derivations
│
└── README.md
Lexical analysis is the first phase of a compiler. It reads raw source code character by character and groups characters into meaningful units called tokens. Each token is classified by type (keyword, identifier, number, operator, etc.) and associated with a line number for error reporting.
The task for Phase 1 was to:
- Define Regular Expressions (REs) for each token category
- Design corresponding Finite Automata (FAs) for each RE
- Implement a working lexical analyzer in C++ that:
- Reads source code from a plain text file
- Classifies every lexeme into its correct token category
- Records the line number of each token
- Outputs all recognized tokens to a file
- Logs any unrecognized characters as errors
| Token Type | Examples |
|---|---|
KEYWORD |
int, float, for, if, else, return, agar, magar, loop |
IDENTIFIER |
main, _num1, num_2, sum, result |
NUMBER |
10, 20, 3.14, 0 |
OPERATOR |
+, -, *, /, =, <, >, <<, ==, != |
PUNCTUATION |
(, ), {, }, [, ], ,, ; |
STRING LITERAL |
"Sum is greater than 20" |
The language includes Urdu-derived keywords alongside standard C++ keywords:
| Custom Keyword | Meaning |
|---|---|
agar |
if |
magar |
else |
loop |
custom loop construct |
| File | Role | Direction |
|---|---|---|
sourcecode.txt |
The source code to be lexically analyzed | Input |
code.cpp |
The lexical analyzer program | Source |
Token.txt |
All recognized tokens with type and line number | Output |
Error.txt |
All unrecognized/invalid tokens with line number | Output |
RE and FA.pdf |
Theoretical documentation of REs and FAs for each token class | Documentation |
- The program opens
sourcecode.txtand reads the entire file into a string - It scans character by character using a
processCode()function - Comments (
//and/* */) are detected and skipped - String literals between double quotes are captured whole
- Each other sequence is matched against: letters/underscore (→ keyword or identifier), digits (→ number), known operator characters (→ operator), or known punctuation characters (→ punctuation)
- Unmatched characters are written to the error log
- All tokens are written to
Token.txtwith format:TYPE: lexeme (Line N)
Prerequisites: A C++ compiler (g++ recommended)
# Step 1: Place your source code in sourcecode.txt
# Step 2: Compile the lexer
g++ -o lexer code.cpp
# Step 3: Run the lexer
./lexer
# Step 4: View output
cat Token.txt
cat Error.txtExpected output in Token.txt:
KEYWORD: int (Line 1)
IDENTIFIER: main (Line 1)
PUNCTUATION: ( (Line 1)
PUNCTUATION: ) (Line 1)
...
Syntax analysis is the second phase of a compiler. It takes the stream of tokens produced by the lexical analyzer and checks whether they conform to the grammatical rules of the language. The result is a parse tree (or derivation sequence) that represents the hierarchical structure of the source program.
This phase implements a top-down, recursive descent LL(1) parser — meaning it parses input from Left to right, produces a Leftmost derivation, and uses 1 token of lookahead.
The task for Phase 2 was to:
- Define a context-free grammar (CFG) for the language, eliminating left recursion and left factoring to make it LL(1) compatible
- Compute First and Follow sets for all non-terminals
- Construct the LL(1) Parsing Table with panic-mode error recovery entries
- Implement a recursive descent parser in C++ that:
- Reads tokens from the token file produced by the scanner
- Applies grammar productions to derive the program structure
- Outputs each production applied as a parse tree derivation
- Reports syntax errors with line numbers
The grammar covers the following constructs:
| Construct | Production |
|---|---|
| Function definition | Function → Type identifier < ArgList > CompoundStmt |
| Argument list | ArgList → Arg ArgList' with comma-separated repetition |
| Type | Type → int | float |
| Variable declaration | Declaration → Type IdentList ; |
| Statements | Stmt → ForStmt | IfStmt | LoopStmt | Declaration | Expr ; | CompoundStmt | ; |
| For loop | ForStmt → for < Expr ; OptExpr ; OptExpr > Stmt |
| Custom if/else | IfStmt → agar < Expr > Stmt MagarPart |
| Custom loop | LoopStmt → loop < Expr > Stmt |
| Expression hierarchy | Expr → Rvalue → Mag → Term → Factor |
| Grouping | Factor → < Expr > | identifier | number |
First and Follow sets were computed for all 20+ non-terminals in the grammar. These sets are the mathematical foundation used to build the LL(1) parsing table — they determine which production to apply given the current input token.
Sample sets:
| Non-Terminal | FIRST Set | FOLLOW Set |
|---|---|---|
Function |
{int, float} |
{$} |
Stmt |
{for, loop, agar, {, int, float, ;, <, identifier, number} |
{}, magar, $, ...} |
Expr |
{<, identifier, number} |
{;, >} |
MagarPart |
{magar, ε} |
Follow(Stmt) |
The parsing table maps every (Non-Terminal, Input Token) pair to the production to apply. Every non-terminal includes panic-mode error recovery — when an unexpected token is encountered, the parser skips input tokens until it finds a token in the non-terminal's Follow set, at which point parsing resumes.
| File | Role | Direction |
|---|---|---|
sourcecode.txt |
Source code to be compiled | Input |
scanner.cpp |
Lexical analyzer — produces tokens from source | Source / Step 1 |
Token.txt |
Token stream output from scanner | Intermediate Output |
TokenError.txt |
Lexical errors detected during scanning | Output |
main.cpp |
LL(1) recursive descent parser | Source / Step 2 |
Error.txt |
Syntax errors detected during parsing | Output |
parse_tree.txt |
Grammar productions applied during parsing | Output |
grammar, first and follow and LL1 table/revised grammar.txt |
Formal grammar rules for the language | Documentation |
grammar, first and follow and LL1 table/first and follow.txt |
Computed First and Follow sets | Documentation |
grammar, first and follow and LL1 table/LL1.docx |
LL(1) parsing table with panic-mode recovery | Documentation |
- Scanner runs first:
scanner.cppreadssourcecode.txtand writes classified tokens toToken.txtand any lexical errors toTokenError.txt - Parser reads tokens:
main.cppopensToken.txtand parses each line to reconstruct the token stream intoTokenstructs (type, lexeme, line number) - Recursive descent parsing: Starting from
parseFunction(), the parser calls sub-functions for each non-terminal. Each function checks the current lookahead token and applies the matching production - Production output: Every production applied is written to
parse_tree.txt, forming a textual derivation trace - Error handling: When a mismatch is found, the error is logged to
Error.txtwith the line number and expected token, then panic-mode recovery is applied
Prerequisites: A C++ compiler (g++ recommended)
# Step 1: Place your source code in sourcecode.txt
# Step 2: Compile and run the scanner first
g++ -o scanner scanner.cpp
./scanner
# This generates Token.txt and TokenError.txt
# Step 3: Compile and run the parser
g++ -o parser main.cpp
./parser
# This generates parse_tree.txt and Error.txt
# Step 4: View outputs
cat Token.txt # Token stream
cat TokenError.txt # Lexical errors
cat parse_tree.txt # Parse derivations
cat Error.txt # Syntax errorsint main() {
int _num1 = 10;
int num_2 = 20;
int sum = _num1 + num_2;
if (sum > 20) {
cout << "Sum is greater than 20" << endl;
} else {
cout << "Sum is less than or equal to 20" << endl;
}
return 0;
}KEYWORD: int (Line 1)
IDENTIFIER: main (Line 1)
PUNCTUATION: ( (Line 1)
PUNCTUATION: ) (Line 1)
PUNCTUATION: { (Line 1)
KEYWORD: int (Line 2)
IDENTIFIER: _num1 (Line 2)
OPERATOR: = (Line 2)
NUMBER: 10 (Line 2)
PUNCTUATION: ; (Line 2)
...
STRING LITERAL: "Sum is greater than 20" (Line 6)
...
Function → Type identifier < ArgList > CompoundStmt
Type → int
ArgList → Arg ArgList'
Arg → Type identifier
ArgList' → ε
CompoundStmt → { StmtList }
StmtList → ε
Each token category was formally defined using regular expressions before implementation:
- Identifiers:
^[_a-zA-Z][_a-zA-Z0-9]*$ - Numbers:
^[+-]?([0-9]+(\.[0-9]+)?([Ee][+-]?[0-9]+)?)$ - Operators: Union of all single and double-character operator symbols
- Keywords: Union of all reserved words including
agar,magar,loop
For each RE, a corresponding Finite Automaton (FA) was designed with defined states, transitions, start states, and accept states. These are documented with full diagrams in RE and FA.pdf.
The grammar was designed to satisfy the LL(1) property — no left recursion and no ambiguity in production selection. This was verified by confirming that for every non-terminal, its First/Follow sets have no conflicts. The recursive descent parser is a direct implementation of the LL(1) parsing algorithm.
This project delivered a complete compiler front-end pipeline as an educational implementation:
- A fully functional lexical analyzer that tokenizes C++ and custom-language source code, handles comments, string literals, compound operators, and underscore-prefixed identifiers
- A formally defined, LL(1)-compatible context-free grammar with proper left-factoring and elimination of left recursion
- Mathematically computed First and Follow sets for all grammar non-terminals
- A complete LL(1) parsing table with panic-mode error recovery for all non-terminals
- A working recursive descent parser that produces a derivation trace and reports syntax errors with line numbers
- Integration of Urdu-inspired custom keywords (
agar,magar,loop) into a formal grammar, demonstrating extensibility of language design
Both phases together implement the theoretical concepts of a compiler front-end as taught in the Compiler Construction course, covering the full pipeline from raw source text to a structured parse tree.
- Language: C++
- Standard Libraries:
<iostream>,<fstream>,<string>,<vector> - Compiler: g++ (GCC)
- Documentation: PDF (RE & FA diagrams), DOCX (LL(1) table), plain text
MIT License
Copyright (c) 2024 Ali Zaman
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.