I'm trying to create a very basic compiler, and I'm working on the lexer. I decided to store keywords and operators in an unordered_set, and use regex to match identifiers and literals (no comments yet).
Tokens.h
#ifndef TOKENS_H
#define TOKENS_H
#include<unordered_set>
#include<string>
namespace AVSL
{
enum class TokenType
{
Identifier,
Keyword,
String,
Literal,
Comment,
Operator,
Delim
};
static const std::unordered_set<std::string> keywords = { "var", "for", "while", "print", "constant"};
static const std::unordered_set<std::string> operators = { "+", "-", "*", "/", "=", "==", "+=", "-=", "*=", "/=" };
static const std::unordered_set<char> delims = { '(', ')', ';' };
static const std::unordered_set<char> whitespace = { '\n', '\r', '\t', ' ' };
struct Token
{
std::string tokenValue;
TokenType tokenType;
Token() = delete;
Token(const std::string& tokenValue_, TokenType tokenType_): tokenValue(tokenValue_), tokenType(tokenType_){ /* empty */ }
Token(const Token& token): tokenValue(token.tokenValue), tokenType(token.tokenType) { /* empty */ }
};
}
#endif
Lexer.h
#ifndef LEXER_H
#define LEXER_H
#include "Tokens.h"
using LineNo = unsigned int;
namespace AVSL
{
std::vector<Token> Tokenize(std::string filename);
Token getToken(std::string buffer, LineNo line);
}
#endif
Lexer.cpp
#include "Lexer.h"
#include<fstream>
#include<iostream>
#include<regex>
namespace AVSL
{
Token getToken(std::string buffer, LineNo line)
{
if (operators.find(buffer) != operators.end())
{
return Token(buffer, TokenType::Operator);
}
else if (keywords.find(buffer) != keywords.end())
{
return Token(buffer, TokenType::Keyword);
}
std::regex iden("[a-zA-Z][a-zA-Z0-9_]*");
std::regex str("\".*\"");
std::regex lit("^[0-9]+$");
//std::regex;
if (std::regex_match(buffer, iden))
{
return Token(buffer, TokenType::Identifier);
}
if (std::regex_match(buffer, str))
{
return Token(buffer, TokenType::String);
}
if (std::regex_match(buffer, lit))
{
return Token(buffer, TokenType::Literal);
}
/* No support for comments yet*/
/* Will only reach here if all else fails, invalid string */
std::cout << "Invalid expression at line number " << line << " : " << buffer;
std::cin.get();
exit(0);
}
std::vector<Token> Tokenize(std::string filename)
{
std::ifstream file(filename, std::ios::in);
if (file.fail())
{
std::cout << "Unable to open file!\n";
std::cin.get();
exit(0);
}
LineNo line = 1;
std::string buffer = "";
char ch;
std::vector<Token> tokens;
while (file >> std::noskipws >> ch)
{
if (ch == '\n' || ch == '\r')
line += 1;
if (delims.find(ch) != delims.end())
{
if (buffer != "")
{
tokens.push_back(getToken(buffer, line));
buffer = "";
}
tokens.push_back(Token(std::string(1, ch), TokenType::Delim));
continue;
}
else if (whitespace.find(ch) != whitespace.end())
{
if (buffer != "")
{
tokens.push_back(getToken(buffer, line));
buffer = "";
}
continue;
}
else
{
buffer += ch;
}
}
//std::cout << line << "\n";
return tokens;
}
}
main.cpp
#include "Tokens.h"
#include "Lexer.h"
#include<iostream>
#include<unordered_map>
#include<fstream>
int main(int argc, char** argv)
{
std::unordered_map<AVSL::TokenType, std::string> tokenMap = { {AVSL::TokenType::Keyword, "Keyword"}, {AVSL::TokenType::Identifier, "Identifier"}, {AVSL::TokenType::Operator, "Operator"}, {AVSL::TokenType::Literal, "Literal"}, {AVSL::TokenType::Delim, "Delim" } };
auto vec = AVSL::Tokenize("dummy.txt");
for (auto x : vec)
{
std::cout << x.tokenValue << "->" << tokenMap.find(x.tokenType)->second << "\n";
}
std::cin.get();
return 0;
}
The lexer works fine so far. On a dummy file:
var i = 42;
It prints out:
var->Keyword
i->Identifier
=->Operator
42->Literal
;->Delim
Also, on invalid inputs, it will print out the line number and invalid string:
var i = 42;
i += 2a;
Invalid expression at line number 5 : 2a
Any advice/comments would be appreciated. I'm mostly concerned of error handling. Right now, all it does is print to stdout and exit the program.