Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.31.2)

include(FetchContent)

set(BUILD_SHARED_LIBS OFF CACHE BOOL "Link libraries statically")

if(CMAKE_CXX_COMPILER_ID MATCHES "CNU|Clang")
Expand All @@ -12,8 +14,17 @@ if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()


#add_subdirectory("thirdparty/lib")

FetchContent_Declare(
cxxopts
GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git
GIT_TAG "v3.3.1"
)

FetchContent_MakeAvailable(cxxopts)

project(oxygen LANGUAGES CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

Expand All @@ -27,9 +38,16 @@ add_executable(oxygen "${MY_SOURCES}")
target_compile_definitions("${CMAKE_PROJECT_NAME}" PUBLIC RESOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/")
#target_compile_definitions("${CMAKE_PROJECT_NAME}" PUBLIC RESOURCE_PATH="./resources/")

# ----- Preprocessor -----
if(include_dump_args_flag)
target_compile_definitions("${CMAKE_PROJECT_NAME}" PRIVATE DBG_OXY_DUMP_ARGS=1)
endif()

target_include_directories(oxygen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/")
#target_link_libraries(oxygen PRIVATE lib)

target_link_libraries(oxygen PRIVATE cxxopts::cxxopts)

# ----- LLVM ------
find_package(LLVM REQUIRED CONFIG)

Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Oxygen

A frankenstein compiler of C-like languages (C/C++) and Rust with its borrow system, using LLVM as backend.
Expand All @@ -11,21 +10,27 @@ A frankenstein compiler of C-like languages (C/C++) and Rust with its borrow sys
The project rely on CMake and LLVM, specifically, CMake 3.31.2, LLVM 19 respectively.
It also uses C++ 23 standartd.

## Installation
Other dependencies are downloaded using CMake.

## Building

First, ensure you have CMake, LLVM and any sutable C++ compiler installed.
Then clone repo and run CMake

```bash
git clone ...
cd oxygen
mkdir build/
cd build
cmake ..
make
```

## Usage/Examples

Well, as the compiler in earliest stage of development, there aren't much of an example to give,
The compiler accepts input file, as well as output file with `-o` flag.
Warnings can be ignored using `--Wignore` or produce errors instead, with `--Werror` flags.

If defined `DBG_OXY_DUMP_ARGS` in source, or `-Dinclude_dump_args_flag=1` in CMake via shell,
the `-dArgs` flag will be availabe, to dump options structure.

The compiler in earliest stage of development, so there aren't much of an example to give,
nor what syntax or features it implies
69 changes: 69 additions & 0 deletions include/Args/CompilerOptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once

#include "Diagnostic/DiagnosticLevel.hpp"
#include "Diagnostic/DiagnosticsEngine.hpp"
#include <iostream>
#include <string>
#include <cxxopts.hpp>

struct CompilerOptions {
bool werror = false; // Produce errors instead of warnings
bool wignore = false; // Disable all warnings

std::string inputFile;
std::string outputFile = "a.out";

int context = 1;

/* -=-=-=- DEBUG -=-=-=- */
bool dumpArgs = false;
};

CompilerOptions parseArguments(int argc, char** argv) {
CompilerOptions opts;

cxxopts::Options options(argv[0], "Oxygen compiler");
options.positional_help("input file").show_positional_help();

options.add_options()
("h,help", "Print help")

("Wignore", "Disable all warnings", cxxopts::value<bool>(opts.wignore))
("Werror", "Produce errors instead of warnings", cxxopts::value<bool>(opts.werror))

("o", "Output file", cxxopts::value<std::string>(opts.outputFile))
("input", "Input file", cxxopts::value<std::string>(opts.inputFile))

("context", "Lines of context around diagnostic", cxxopts::value<int>(opts.context)->default_value("1"))

#ifdef DBG_OXY_DUMP_ARGS
("dArgs", "Dumps parsed arguments to standard output", cxxopts::value<bool>(opts.dumpArgs))
#endif
; // options.add_options()


options.parse_positional({"input"});

auto result = options.parse(argc, argv);

if (result.count("help")) {
std::cout << options.help() << "\n";
exit(0);
}

if (!result.count("input")) {
std::cerr << "No input file provded. Use --help\n";
exit(1);
}

if(opts.dumpArgs) {
std::cout << "PARSED ARGS:\n";
std::cout << " Wignore -> " << opts.wignore << "\n";
std::cout << " Werror -> " << opts.werror << "\n";
std::cout << " output -> " << opts.outputFile << "\n";
std::cout << " input -> " << opts.inputFile << "\n";
std::cout << " context -> " << opts.context << "\n";
}

return opts;
}
13 changes: 13 additions & 0 deletions include/Diagnostic/Colors.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

namespace Colors {
constexpr const char* Reset = "\033[0m";
constexpr const char* Red = "\033[31m";
constexpr const char* Yellow = "\033[33m";
constexpr const char* Blue = "\033[34m";
constexpr const char* Cyan = "\033[36m";
constexpr const char* Gray = "\033[90m";

constexpr const char* Bold = "\033[1m";
constexpr const char* Underline = "\033[4m";
}
16 changes: 16 additions & 0 deletions include/Diagnostic/Diagnostic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include "Diagnostic/DiagnosticLevel.hpp"
#include "Diagnostic/SourceLocation.hpp"
#include "Diagnostic/DiagnosticNote.hpp"
#include <string>
#include <vector>

struct Diagnostic {
DiagnosticLevel level;
SourceLocation loc;
std::string message;
std::string suggestion;
int length = 1;
std::vector<DiagnosticNote> notes;
};
7 changes: 7 additions & 0 deletions include/Diagnostic/DiagnosticLevel.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

enum class DiagnosticLevel {
Info,
Warning,
Error
};
10 changes: 10 additions & 0 deletions include/Diagnostic/DiagnosticNote.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include "Diagnostic/SourceLocation.hpp"
#include <string>

struct DiagnosticNote {
SourceLocation loc;
int length = 1;
std::string message;
};
196 changes: 196 additions & 0 deletions include/Diagnostic/DiagnosticsEngine.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#pragma once

#include <algorithm>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "Diagnostic/Diagnostic.hpp"
#include "Diagnostic/SourceManager.hpp"
#include "Diagnostic/DiagnosticLevel.hpp"
#include "Diagnostic/SourceLocation.hpp"
#include "Diagnostic/Colors.hpp"

class DiagnosticsEngine {
public:
DiagnosticsEngine(SourceManager& sm, int contextLines = 1, bool ignoreWarnings = false, bool warningsAreErrors = false)
: srcMgr(sm), context(contextLines), ignoreWarnings(ignoreWarnings), warningsAsErrors(warningsAreErrors) {}

void report(const Diagnostic& d) {
if (d.level == DiagnosticLevel::Warning && ignoreWarnings)
return;

Diagnostic newD;
if (d.level == DiagnosticLevel::Warning && warningsAsErrors) {
newD.loc = d.loc;
newD.level = DiagnosticLevel::Error;
newD.length = d.length;
newD.message = d.message;
newD.notes = d.notes;
newD.suggestion = d.suggestion;
} else {
newD = d;
}

diags.push_back(newD);
printDiagnostic(newD);
}

bool hasErrors() const {
for (auto &d : diags) {
if (d.level == DiagnosticLevel::Error)
return true;
}
return false;
}

void setContext(int ctx) { context = ctx; }

private:
SourceManager& srcMgr;
int context;
std::vector<Diagnostic> diags;
bool ignoreWarnings = false;
bool warningsAsErrors = false;

static std::string expandTabs(const std::string& s, unsigned tabSize = 4) {
std::string out;
out.reserve(s.size());
unsigned col = 0;
for (char ch : s) {
if (ch == '\t') {
unsigned spaces = tabSize - (col % tabSize);
out.append(spaces, ' ');
col += spaces;
} else {
out.push_back(ch);
++col;
}
}
return out;
}

static const char* levelToString(DiagnosticLevel level) {
switch (level) {
case DiagnosticLevel::Info: return "note";
case DiagnosticLevel::Warning: return "warning";
case DiagnosticLevel::Error: return "error";
}
return "";
}

static const char* levelColor(DiagnosticLevel level) {
switch (level) {
case DiagnosticLevel::Info: return Colors::Blue;
case DiagnosticLevel::Warning: return Colors::Yellow;
case DiagnosticLevel::Error: return Colors::Red;
}
return "";
}

void printDiagnostic(const Diagnostic& d) {
// header -> file:line:column: <level>: <message>
std::ostringstream header;
header << d.loc.file << ":" << d.loc.line << ":" << d.loc.column << ": ";

std::string lev = levelToString(d.level);
const char* color = levelColor(d.level);

std::cerr << color << header.str() << lev << ": " << Colors::Bold << d.message << Colors::Reset << "\n";

if (!d.suggestion.empty()) {
std::cerr << Colors::Gray << " hint: " << Colors::Reset << d.suggestion << "\n";
}

printSourceSnippet(d.loc, d.length);

for(const auto &note : d.notes) {
std::ostringstream noteHeader;
noteHeader << note.loc.file << ":" << note.loc.line << ":" << note.loc.column << ": ";
std::cerr << Colors::Cyan << noteHeader.str() << "note: " << Colors::Reset << note.message << "\n";
printSourceSnippet(note.loc, note.length);
}
}

void printSourceSnippet(const SourceLocation& loc, int length) {
const auto& lines = srcMgr.getLines(loc.fileId);
if(lines.empty()) {
std::cerr << Colors::Gray << " (source not available)\n" << Colors::Reset;
return;
}

int totalLines = (int)lines.size();
int lineIdx = std::max(1, loc.line);
if (lineIdx > totalLines) {
std::cerr << Colors::Gray << " (location outside file: line " << loc.line << " > " << totalLines << ")\n" << Colors::Reset;
return;
}

int start = std::max(1, loc.line - context);
int end = std::min(totalLines, loc.line + context);

int width = 1;
int maxLine = end;
while(maxLine >= 10) { maxLine /= 10; ++width; }

for (int L = start; L <= end; ++L) {
std::string rawLine = lines[L - 1];
std::string expanded = expandTabs(rawLine);

std::ostringstream gutter;
if (L == loc.line) {
gutter << Colors::Red << ">" << Colors::Reset << " ";
} else {
gutter << " ";
}

std::ostringstream ln;
ln << std::setw(width) << L;

std::cerr << gutter.str() << Colors::Gray << ln.str() << " | " << Colors::Reset;
//std::cerr << expanded << "\n";

if (L == loc.line) {
int col = std::max(1, loc.column);

int lineLen = (int)expanded.size();
int startCol = std::min(col, lineLen + 1);
int maxAvail = std::max(0, lineLen - (startCol - 1));
int caretCount = (length > 0) ? std::min(length, std::max(1, maxAvail)) : 1;

int prefixLen = (startCol > 1) ? startCol - 1 : 0;
if (prefixLen > expanded.size()) prefixLen = expanded.size();

int errorLen = 0;
if (prefixLen < expanded.size()) {
errorLen = std::min((int)(expanded.size() - prefixLen), caretCount);
}

if (errorLen > 0) {
std::cerr << expanded.substr(0, prefixLen)
<< Colors::Bold << Colors::Red
<< expanded.substr(prefixLen, errorLen)
<< Colors::Reset
<< expanded.substr(prefixLen + errorLen) << "\n";
} else {
std::cerr << expanded << "\n";
}

std::ostringstream prefixSpaces;
prefixSpaces << " " << std::string(width, ' ') << " | ";

std::cerr << prefixSpaces.str();

if (startCol > 1)
std::cerr << std::string(startCol - 1, ' ');

std::cerr << Colors::Red << "^";
for (int i = 1; i < caretCount; ++i) std::cerr << "~";
std::cerr << Colors::Reset << "\n";
} else {
std::cerr << expanded << "\n";
}
}
}
};
Loading