commit 471ae799ee654084667d3635fd959ca9f4e0b452
parent 68de20aebb7b522651dd591789127470eaeaeefc
Author: Achuthan TM <achuthantm05@gmail.com>
Date: Fri, 24 Apr 2026 07:47:08 +0530
Week 6: C++ compiler core updates, emitter logic, and PII FPGA wrapper (Part 3: Finalizing)
Diffstat:
4 files changed, 76 insertions(+), 45 deletions(-)
diff --git a/src/nfa.h b/src/nfa.h
@@ -10,9 +10,7 @@
struct NFAState {
int id;
bool isAccept;
- // Map from character to set of destination state IDs
std::map<unsigned char, std::set<int>> transitions;
-
explicit NFAState(int id, bool isAccept = false) : id(id), isAccept(isAccept) {}
};
@@ -30,13 +28,11 @@ public:
class NFABuilder {
public:
- // Global state counter to ensure unique IDs across all NFAs
static int globalStateCounter;
-
static std::unique_ptr<NFA> build(ASTNode* root, int regexIdx);
private:
- static void linearize(ASTNode* node, int& posCounter, std::map<int, unsigned char>& posToChar, std::set<int>& dotPositions);
+ static void linearize(ASTNode* node, int& posCounter, std::map<int, std::set<unsigned char>>& posToChars, std::set<int>& dotPositions);
static void computeNullableFirstLast(ASTNode* node);
static void computeFollowpos(ASTNode* node, std::map<int, std::set<int>>& followpos);
};
diff --git a/src/parser.cpp b/src/parser.cpp
@@ -10,6 +10,7 @@ std::string nodeTypeToString(ASTNodeType type) {
case ASTNodeType::STAR: return "STAR";
case ASTNodeType::PLUS: return "PLUS";
case ASTNodeType::OPTIONAL: return "OPTIONAL";
+ case ASTNodeType::CHAR_CLASS: return "CHAR_CLASS";
default: return "UNKNOWN";
}
}
@@ -26,19 +27,12 @@ std::unique_ptr<ASTNode> Parser::parse() {
// Expression -> Term ( '|' Term )*
std::unique_ptr<ASTNode> Parser::parseExpression() {
- if (check(TokenType::PIPE)) {
- error("Empty alternation branch (missing left side)");
- }
+ if (check(TokenType::PIPE)) error("Empty alternation branch");
auto left = parseTerm();
-
while (match(TokenType::PIPE)) {
- if (isAtEnd() || peek().type == TokenType::RPAREN || peek().type == TokenType::PIPE || peek().type == TokenType::END_OF_INPUT) {
- error("Empty alternation branch");
- }
auto right = parseTerm();
left = std::make_unique<UnionNode>(std::move(left), std::move(right));
}
-
return left;
}
@@ -84,18 +78,34 @@ std::unique_ptr<ASTNode> Parser::parseAtom() {
if (match(TokenType::LPAREN)) {
auto node = parseExpression();
- if (!match(TokenType::RPAREN)) {
- error("Unmatched parenthesis: expected ')'");
+ if (!match(TokenType::RPAREN)) error("Unmatched parenthesis");
+ return node;
+ }
+
+ if (match(TokenType::LBRACKET)) {
+ auto node = std::make_unique<CharClassNode>();
+ while (!check(TokenType::RBRACKET) && !isAtEnd()) {
+ if (peek().type == TokenType::END_OF_INPUT) error("Unterminated char class");
+ char start = advance().value;
+ if (peek().value == '-' && pos + 1 < tokens.size() &&
+ (tokens[pos+1].type == TokenType::LITERAL || tokens[pos+1].type == TokenType::DOT)) {
+ advance(); // consume '-'
+ char end = advance().value;
+ for (int c = start; c <= end; ++c) node->characters.insert(static_cast<unsigned char>(c));
+ } else {
+ node->characters.insert(static_cast<unsigned char>(start));
+ }
}
+ if (!match(TokenType::RBRACKET)) error("Expected ']' after char class");
return node;
}
if (peek().type == TokenType::STAR || peek().type == TokenType::PLUS || peek().type == TokenType::QUESTION) {
- error("Quantifier '" + tokenTypeToString(peek().type) + "' applied to nothing");
+ error("Quantifier applied to nothing");
}
error("Unexpected token: " + tokenTypeToString(peek().type));
- return nullptr; // Should not reach here
+ return nullptr;
}
const Token& Parser::peek() const {
diff --git a/src/parser.h b/src/parser.h
@@ -13,7 +13,8 @@ enum class ASTNodeType {
UNION,
STAR,
PLUS,
- OPTIONAL
+ OPTIONAL,
+ CHAR_CLASS
};
class ASTNode {
@@ -21,7 +22,6 @@ public:
ASTNodeType type;
virtual ~ASTNode() = default;
- // The following fields are populated by the NFA builder (Stage 2), NOT the parser.
bool nullable = false;
std::set<int> firstpos;
std::set<int> lastpos;
@@ -34,13 +34,20 @@ std::string nodeTypeToString(ASTNodeType type);
class LiteralNode : public ASTNode {
public:
char value;
- int position; // Used for Glushkov's construction
+ int position;
explicit LiteralNode(char v) : ASTNode(ASTNodeType::LITERAL), value(v), position(-1) {}
};
+class CharClassNode : public ASTNode {
+public:
+ std::set<unsigned char> characters;
+ int position;
+ CharClassNode() : ASTNode(ASTNodeType::CHAR_CLASS), position(-1) {}
+};
+
class DotNode : public ASTNode {
public:
- int position; // Used for Glushkov's construction
+ int position;
DotNode() : ASTNode(ASTNodeType::DOT), position(-1) {}
};
@@ -90,10 +97,10 @@ private:
const std::vector<Token>& tokens;
size_t pos;
- std::unique_ptr<ASTNode> parseExpression(); // Union
- std::unique_ptr<ASTNode> parseTerm(); // Concatenation
- std::unique_ptr<ASTNode> parseFactor(); // Quantifiers
- std::unique_ptr<ASTNode> parseAtom(); // Literals, Dot, Parens
+ std::unique_ptr<ASTNode> parseExpression();
+ std::unique_ptr<ASTNode> parseTerm();
+ std::unique_ptr<ASTNode> parseFactor();
+ std::unique_ptr<ASTNode> parseAtom();
const Token& peek() const;
const Token& advance();
diff --git a/src/parser_tester.cpp b/src/parser_tester.cpp
@@ -7,22 +7,27 @@
#include "parser.h"
#include "nfa.h"
-std::string trim(const std::string& str) {
+std::string trim(const std::string &str)
+{
size_t first = str.find_first_not_of(" \t\r\n");
- if (first == std::string::npos) return "";
+ if (first == std::string::npos)
+ return "";
size_t last = str.find_last_not_of(" \t\r\n");
return str.substr(first, (last - first + 1));
}
-int main(int argc, char* argv[]) {
- if (argc < 2) {
+int main(int argc, char *argv[])
+{
+ if (argc < 2)
+ {
std::cerr << "Usage: " << argv[0] << " <regex_file> [test_strings_file]" << std::endl;
return 1;
}
std::string regexFilename = argv[1];
std::ifstream regexFile(regexFilename);
- if (!regexFile.is_open()) {
+ if (!regexFile.is_open())
+ {
std::cerr << "Failed to open " << regexFilename << std::endl;
return 1;
}
@@ -30,13 +35,17 @@ int main(int argc, char* argv[]) {
// Optional: Test strings for validation
std::string testFilename = (argc > 2) ? argv[2] : "";
std::vector<std::string> testStrings;
- if (!testFilename.empty()) {
+ if (!testFilename.empty())
+ {
std::ifstream testFile(testFilename);
- if (testFile.is_open()) {
+ if (testFile.is_open())
+ {
std::string testLine;
- while (std::getline(testFile, testLine)) {
+ while (std::getline(testFile, testLine))
+ {
std::string trimmed = trim(testLine);
- if (trimmed.empty() || trimmed[0] == '#') continue;
+ if (trimmed.empty() || trimmed[0] == '#')
+ continue;
testStrings.push_back(trimmed);
}
}
@@ -49,24 +58,30 @@ int main(int argc, char* argv[]) {
std::vector<std::string> originalRegexes;
// Core Pipeline: Regex -> Tokens -> AST -> NFA
- while (std::getline(regexFile, line)) {
+ while (std::getline(regexFile, line))
+ {
lineNum++;
std::string trimmedLine = trim(line);
- if (trimmedLine.empty() || trimmedLine[0] == '#') continue;
+ if (trimmedLine.empty() || trimmedLine[0] == '#')
+ continue;
originalRegexes.push_back(trimmedLine);
Lexer lexer(trimmedLine, lineNum);
- try {
+ try
+ {
std::vector<Token> tokens = lexer.tokenize();
Parser parser(tokens);
auto ast = parser.parse();
-
+
// Glushkov NFA construction
auto nfa = NFABuilder::build(ast.get(), regexIdx++);
- if (nfa) {
+ if (nfa)
+ {
nfas.push_back(std::move(nfa));
}
- } catch (const std::exception& e) {
+ }
+ catch (const std::exception &e)
+ {
std::cerr << "Error processing regex '" << trimmedLine << "': " << e.what() << std::endl;
}
}
@@ -74,20 +89,23 @@ int main(int argc, char* argv[]) {
std::cout << "Successfully built " << nfas.size() << " NFAs." << std::endl;
// Optional Validation: NFA Simulation
- if (!testStrings.empty()) {
+ if (!testStrings.empty())
+ {
std::cout << "\n--- NFA Simulation Results ---" << std::endl;
- for (const auto& testStr : testStrings) {
+ for (const auto &testStr : testStrings)
+ {
std::cout << "String: \"" << testStr << "\"" << std::endl;
- for (size_t i = 0; i < nfas.size(); ++i) {
+ for (size_t i = 0; i < nfas.size(); ++i)
+ {
bool matches = nfas[i]->simulate(testStr);
- std::cout << " Regex [" << i << "] (" << originalRegexes[i] << "): "
+ std::cout << " Regex [" << i << "] (" << originalRegexes[i] << "): "
<< (matches ? "MATCH" : "NO MATCH") << std::endl;
}
std::cout << std::endl;
}
}
- // Future: Stage 3 — Verilog Emitter
+ // Future: Stage 3 - Verilog Emitter
// if (!nfas.empty()) {
// Emitter emitter(nfas);
// emitter.emit("output/");