I wanted to create my own compiler for C and it's the first prototype of the Lexer. Any thoughts on improvements?
Note: I forgot to add comment lines in header files while I was coding, but I assure you code in header files is clear and easy to understand.
Main.cpp
Main code which has Lexer inside
#include "Lists.h"
#include "Tokens.h"
#include <stream>
// checks for is it real end of string
// ex:
// "\" string is still open " string is now closed
// " \\" string is closed
inline static bool is_escape_char(char* c)
{
int i = -1;
while (c[i] == '\\') i--;
return i & 1;
}
int main()
{
char *buffer;
// Read file
{
std::ifstream file("Main.cpp");
assert(file.is_open());
file.seekg(0, std::ios::end);
size_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::cout << "File size: " << file_size << '\n';
buffer = (char*)malloc(file_size + 1);
assert(buffer != nullptr);
file.read(buffer, file_size);
file.close();
buffer[file_size] = '\0';
}
List<Token> tokens;
// for each char in file
for (char *c = buffer; *c != '\0';)
{
char C = c[0], *end;
TokenKind kind;
bool sign = (c[0] == '-') || (c[0] == '+');
// skip if space (' ', '\n', '\t', '\r' ...)
if (isspace(C))
{
c++;
continue;
}
else if (isdigit(c[sign]))
{
// lambda for looping to find the first char which is not a digit or underscore
static auto cond = [](int x) -> int {
return static_cast<int>((isdigit(x) != 0) || (x == '_'));
};
// a function for finding base of number (4324 (decimal), 0x3 (hex)...)
// Tokens.h
uint8_t base = tokenKinds::secondary::number::get_base(c + sign);
kind = TokenKind(tokenKinds::number, base);
// First the first not digit or underscore char
end = find_first_not(c + sign + 1 + tokenKinds::secondary::number::start_indices[base], cond);
// if char is '.' it is a float or double
if (*end == '.')
{
// if number already has a base abort app (ex: 0x32.3213)
if (base != 0)
mexit("Float number with non-decimal system.");
kind.set_sub_kind(tokenKinds::secondary::number::floatK);
end = find_first_not(end + 1, cond);
}
}
else
{
char *underscore = nullptr;
// check for alpha values (int, a, return) starts with underscore (__int64)
if (C == '_')
{
for (underscore = c + 1; isalpha(underscore[0]) == 0; underscore++)
{
if (underscore[0] != '_') // if its finds a char not underscore and not alpha it is not a alpha value instead it is a underscore symbol
{
underscore = nullptr;
break;
}
}
}
if (underscore != nullptr || isalpha(C))
{
static auto cond = [](int x) -> int {
return static_cast<int>((isalnum(x) != 0) || (x == '_'));
};
// find_first_not (Tokens.h) finds first char it is not according to function
end = find_first_not((underscore ? underscore : c) + 1, cond);
kind = TokenKind(tokenKinds::alpha, tokenKinds::secondary::alpha::get_sub_kind(c, end));
}
else
{
// check if comment
if (C == '/')
{
if (c[1] == '/')
{
c = find_first(c + 2, '\n') + 1;
continue;
} // check if multi-line comment
else if (c[1] == '*')
{
c += 2;
while (*c != '/')
c = find_first(c, '*') + 1;
c++;
continue;
}
}
// check doublable symbols (==, *=, !=, <<, >>, ||)
static const char* doublable_and_equal_chars = "<>|&";
static const char* doublable_or_equal_chars = "-+=";
static const char* restof_equal_chars = "*/%!^";
uint8_t length;
if (find_first(doublable_and_equal_chars, C) != doublable_and_equal_chars + 4)
{
length = (C == c[1]) + 1;
length = length + (c[length] == '=');
}
else if (find_first(doublable_or_equal_chars, C) != doublable_or_equal_chars + 3)
length = 1 + (c[1] == C || c[1] == '=');
else if (find_first(restof_equal_chars, C) != restof_equal_chars + 5)
length = 1 + (c[1] == '=');
else
length = 1;
String str(c, length);
kind = TokenKind(tokenKinds::symbol, tokenKinds::secondary::symbol::get_sub_kind(str));
tokens.push_back(Token(str, kind));
// check for strings & chars
bool is_char;
if ((is_char = C == '\'') || C == '"')
{
end = c++;
do
end = find_first(end + 1, C);
while (is_escape_char(end) == false);
tokens.push_back(Token(String(c, end - c), TokenKind(tokenKinds::other, tokenKinds::secondary::other::string + is_char)));
tokens.push_back(Token(String(c = end, 1), kind));
}
c += length;
continue;
}
}
tokens.push_back(Token(String(c, end - c), kind));
c = end;
}
std::cout << "Token amount: " << tokens.size << '\n';
// print tokens
for (ListNode<Token>* t = tokens.start; t != nullptr; t = t->next)
{
std::cout << t->value << '\n';
}
// free the memory
tokens.clear();
return 0;
}
Tokens.h
Header file for Token Struct and some Datas (Keywords, Token kinds, handing ostream (<<) ...)
#pragma once
#include "String.h"
struct TokenKind
{
uint16_t kind;
inline constexpr TokenKind() : kind(0) { }
inline constexpr TokenKind(uint16_t kind) : kind(kind) { }
inline constexpr TokenKind(uint8_t basic, uint16_t sub) : kind(basic | (sub << 2)) { }
constexpr operator uint16_t() const
{
return kind;
}
constexpr inline uint8_t get_basic_kind() const
{
return kind & 0b11;
}
constexpr inline uint16_t get_sub_kind() const
{
return kind >> 2;
}
inline void set_basic_kind(uint8_t value)
{
kind = (kind & (~0b11)) | value;
}
inline void set_sub_kind(uint16_t value)
{
kind = (kind & 0b11) | (value << 2);
}
};
struct Token
{
String str;
TokenKind kind;
Token() : str(), kind() { }
Token(String str, TokenKind kind) : str(str), kind(kind) { }
};
namespace tokenKinds
{
constexpr uint8_t number = 0, alpha = 1, symbol = 2, other = 3;
const char *names[] = {
"number", "alpha", "symbol", "other",
"decimal", "hex", "octal", "binary", "float",
names[3], "keyword",
"unknown", "string", "char"
};
namespace secondary
{
namespace number
{
const char **names = tokenKinds::names + 4;
constexpr uint8_t
decimal = 0,
hex = 1,
octal = 2,
binary = 3,
floatK = 4;
uint8_t start_indices[] = {
0,
2,
1,
2
};
inline constexpr uint8_t get_base(char *str)
{
if (str[0] == '0')
{
if (str[1] == 'x')
return hex;
if (str[1] == 'b')
return binary;
if (isdigit(str[1]) || str[1] == '_')
return octal;
}
return decimal;
}
}
namespace alpha
{
const char **names = tokenKinds::names + 9;
const char *keywords[] = {
"void",
"int",
"unsigned",
"float",
"double",
"bool",
"char",
"short",
"long",
"inline",
"static",
"const",
"return",
"namespace",
"struct",
"operator",
"case",
"if",
"else",
"switch",
"default",
"break",
"continue",
"include",
"define",
"undef"
};
constexpr uint8_t other = 0;
inline uint8_t get_sub_kind(String str)
{
str.set_cstring();
uint8_t v = find_in(str.start, keywords, 26);
str.undo_cstring();
if (v == 26)
return other;
return v + 1;
}
inline uint8_t get_sub_kind(char* start, char* end)
{
char ei = *end;
*end = '\0';
uint8_t v = find_in(start, keywords, 26);
*end = ei;
if (v == 26)
return other;
return v + 1;
}
}
namespace symbol
{
inline uint16_t get_sub_kind(String str)
{
char c = str.start[0];
uint16_t sk = c - '!';
if (c >= 'A')
{
sk -= 'Z' - 'A';
if (c >= 'a')
sk -= 'z' - 'a';
}
if (str.length == 1)
return sk;
c = str.start[1];
sk += (c - '!') << 6;
if (c >= 'A')
{
sk -= ('Z' - 'A') << 6;
if (c >= 'a')
sk -= ('z' - 'a') << 6;
}
return sk;
}
}
namespace other
{
const char **names = tokenKinds::names + 11;
constexpr uint8_t
unknown = 0,
string = 1,
charK = 2;
}
}
} // namespace tokenKinds
inline std::ostream &operator<<(std::ostream &os, TokenKind kind)
{
return os << +kind.get_basic_kind() << ", " << kind.get_sub_kind();
}
std::ostream &operator<<(std::ostream &os, Token token)
{
uint8_t basic_kind = token.kind.get_basic_kind();
uint16_t sub_kind = token.kind.get_sub_kind();
os << "{'type': \033[32m" << tokenKinds::names[basic_kind];
switch (basic_kind)
{
case tokenKinds::number:
os << '-' << tokenKinds::secondary::number::names[sub_kind];
break;
case tokenKinds::alpha:
os << '-' << tokenKinds::secondary::alpha::names[sub_kind != tokenKinds::secondary::alpha::other];
break;
case tokenKinds::other:
os << '-' << tokenKinds::secondary::other::names[sub_kind];
break;
default:
break;
}
return os
<< "\033[0m (\033[90m"
<< token.kind
<< "\033[0m), 'value': '\033[36m"
<< token.str
<< "\033[0m'}";
}
Lists.h
Custom Linked list for storing Tokens
#pragma once
#include <cstdlib>
#include "Basics.h"
template <typename T>
struct ListNode
{
ListNode<T> *next;
T value;
};
#define newv(T, name) T *name = reinterpret_cast<T *>(malloc(sizeof(T))); assert(name != nullptr)
template <typename T>
struct List
{
ListNode<T> *start, *end;
uint16_t size;
List()
{
start = end = nullptr;
size = 0;
}
void clear()
{
ListNode<T> *node = start, *next;
while (true)
{
next = node->next;
free(node);
if (next == nullptr)
break;
node = next;
}
start = end = nullptr;
size = 0;
}
void push_back(T value)
{
newv(ListNode<T>, nnode);
nnode->value = value;
nnode->next = nullptr;
if (size)
end->next = nnode;
else
start = nnode;
end = nnode;
size++;
}
};
template <typename T>
std::ostream &operator<<(std::ostream &os, List<T> &list)
{
os << '{';
ListNode<T> *node = list.start;
for (; node->next != nullptr; node = node->next)
os << node->value << ", ";
return os << node->value << '}';
}
template <typename T>
struct ListIterator
{
ListNode<T> *current, *past;
ListIterator(List<T>& list)
{
if (list.start == nullptr)
mexit("List is empty can't created an iterator!");
current = list.start;
past = nullptr;
}
};
template <typename T>
inline void operator++(ListIterator<T>& it)
{
it.past = it.current;
it.current = it.current->next;
}
Strings.h
#pragma once
#include "Basics.h"
#include <cstdint>
inline char *find_end(char *str)
{
while (*str != '\0')
str++;
return str;
}
inline const char *find_end(const char *str)
{
while (*str != '\0')
str++;
return str;
}
inline char *find_first(char *str, char c)
{
while (*str != '\0' && *str != c)
str++;
return str;
}
inline const char *find_first(const char *str, char c)
{
while (*str != '\0' && *str != c)
str++;
return str;
}
inline char *find_first(char *str, const char *chars)
{
while (*str != '\0')
{
for (const char *c = chars; *c != '\0'; c++)
if (*str == *c)
return str;
str++;
}
return str;
}
inline char *find_first(char *str, int cond(int))
{
while (*str != '\0' && cond(*str) == 0)
str++;
return str;
}
inline char *find_first_not(char *str, int cond(int))
{
while (*str != '\0' && cond(*str))
str++;
return str;
}
inline uint8_t find_in(char *str, const char **list, uint8_t amount)
{
for (uint8_t i = 0; i < amount; i++)
if (strcmp(str, list[i]) == 0)
return i;
return amount;
}
char _string_struct_ei_temp = '\0';
struct String
{
char *start;
uint8_t length;
constexpr String() : start(nullptr), length(0) { }
constexpr String(char *start, uint8_t length) : start(start), length(length) { }
inline void set_cstring() const
{
if (_string_struct_ei_temp != '\0')
mexit("Can't setup multiple cstring at a time!");
_string_struct_ei_temp = start[length];
start[length] = '\0';
}
inline void undo_cstring() const
{
if (_string_struct_ei_temp == '\0')
{
puts("The String is not set as cstring.");
return;
}
start[length] = _string_struct_ei_temp;
_string_struct_ei_temp = '\0';
}
long long to_int() const
{
uint8_t base, start_index;
bool sign = start[0] == '-';
if (start[sign] == '0' && length != sign + 1)
{
switch (start[sign + 1])
{
case 'b':
base = 0b10;
start_index = 2;
break;
case 'x':
base = 0x10;
start_index = 2;
break;
default:
base = 010;
start_index = 1;
break;
}
}
else
{
base = 10;
start_index = 0;
}
long long value = 0;
for (uint8_t i = start_index + sign; i < length; i++)
{
char c = start[i];
if (c != '_')
value = value * base + c - (isdigit(c) ? '0' : (islower(c) ? 'a' : 'A'));
}
return sign ? -value : value;
}
inline double to_float() const
{
set_cstring();
double value = atof(start);
undo_cstring();
return value;
}
};
inline std::ostream &operator<<(std::ostream &os, String &str)
{
for (uint8_t i = 0; i < str.length; i++)
os << str.start[i];
return os;
}
Basics.h
#pragma once
#pragma pack(2)
#undef NDEBUG
#include <cassert>
#include <iostream>
inline void todo(const char *str)
{
std::cout << "TODO: " << str << '\n';
exit(0);
}
inline void mexit(const char *str)
{
puts(str);
exit(0);
}