From bbe2c77243c541ca7e0075149f5be3262eb89523 Mon Sep 17 00:00:00 2001 From: Amlal El Mahrouss Date: Mon, 24 Nov 2025 03:05:29 +0100 Subject: feat! breaking changes on necti sources. Signed-off-by: Amlal El Mahrouss --- src/.keep | 0 src/CompilerKit/AE.h | 136 ++ src/CompilerKit/Compiler.h | 222 +++ src/CompilerKit/Defines.h | 173 ++ src/CompilerKit/ErrorID.h | 29 + src/CompilerKit/ErrorOr.h | 55 + src/CompilerKit/Frontend.h | 138 ++ src/CompilerKit/Macros.h | 28 + src/CompilerKit/PEF.h | 137 ++ src/CompilerKit/Ref.h | 77 + src/CompilerKit/StringKit.h | 94 ++ src/CompilerKit/UUID.h | 826 ++++++++++ src/CompilerKit/Version.h | 15 + src/CompilerKit/XCOFF.h | 43 + src/CompilerKit/ck-osx-san.json | 30 + src/CompilerKit/ck-osx.json | 25 + src/CompilerKit/ck-posix.json | 24 + src/CompilerKit/impl/32x0.h | 93 ++ src/CompilerKit/impl/64x0.h | 100 ++ src/CompilerKit/impl/Aarch64.h | 41 + src/CompilerKit/impl/PowerPC.h | 1557 ++++++++++++++++++ src/CompilerKit/impl/X64.h | 49 + src/CompilerKit/src/AssemblyFactory.cc | 52 + src/CompilerKit/src/Backend/Assembler32x0.cc | 39 + src/CompilerKit/src/Backend/Assembler64x0.cc | 874 ++++++++++ src/CompilerKit/src/Backend/AssemblerAMD64.cc | 1204 ++++++++++++++ src/CompilerKit/src/Backend/AssemblerARM64.cc | 587 +++++++ src/CompilerKit/src/Backend/AssemblerPowerPC.cc | 911 ++++++++++ src/CompilerKit/src/Frontend.cc | 61 + src/CompilerKit/src/Frontend/CCompiler64x0.cc | 1287 +++++++++++++++ src/CompilerKit/src/Frontend/CCompilerARM64.cc | 1286 +++++++++++++++ src/CompilerKit/src/Frontend/CCompilerPower64.cc | 1304 +++++++++++++++ .../src/Frontend/CPlusPlusCompilerAMD64.cc | 906 ++++++++++ src/CompilerKit/src/Linker/DynamicLinker64PEF.cc | 678 ++++++++ src/CompilerKit/src/Macro/CPlusPlusPreprocessor.cc | 893 ++++++++++ src/CompilerKit/src/StringKit.cc | 179 ++ src/CompilerKit/utils/AsmUtils.h | 92 ++ src/CompilerKit/utils/CompilerUtils.h | 116 ++ src/CompilerKit/utils/DylibHelpers.h | 63 + src/DebuggerKit/CommonCLI.inl | 23 + src/DebuggerKit/DebuggerContract.h | 52 + src/DebuggerKit/NeKernelContract.h | 60 + src/DebuggerKit/POSIXMachContract.h | 147 ++ src/DebuggerKit/Platform.h | 37 + src/DebuggerKit/Version.h | 15 + src/DebuggerKit/dk-nekernel.json | 17 + src/DebuggerKit/dk-osx.json | 18 + src/DebuggerKit/src/NeKernelContract.cc | 90 + src/DebuggerKit/src/NeKernelContractCLI.cc | 100 ++ src/DebuggerKit/src/POSIXMachContractCLI.cc | 94 ++ src/LibC++/.gitignore | 1 + src/LibC++/__abi+unreachable.cc | 17 + src/LibC++/__abi.h | 15 + src/LibC++/__power64.inc | 39 + src/LibC++/base_alloc.h | 43 + src/LibC++/base_exception.h | 37 + src/LibC++/base_math.h | 88 + src/LibC++/base_process.h | 45 + src/LibC++/defines.h | 76 + src/LibC++/filesystem.h | 21 + src/LibC++/make_hdrs.sh | 13 + src/LibC++/new.cc | 7 + src/LibC++/new.h | 44 + src/LibC++/utility.h | 30 + src/ThirdParty/Dialogs.h | 1739 ++++++++++++++++++++ 65 files changed, 17292 insertions(+) create mode 100644 src/.keep create mode 100644 src/CompilerKit/AE.h create mode 100644 src/CompilerKit/Compiler.h create mode 100644 src/CompilerKit/Defines.h create mode 100644 src/CompilerKit/ErrorID.h create mode 100644 src/CompilerKit/ErrorOr.h create mode 100644 src/CompilerKit/Frontend.h create mode 100644 src/CompilerKit/Macros.h create mode 100644 src/CompilerKit/PEF.h create mode 100644 src/CompilerKit/Ref.h create mode 100644 src/CompilerKit/StringKit.h create mode 100644 src/CompilerKit/UUID.h create mode 100644 src/CompilerKit/Version.h create mode 100644 src/CompilerKit/XCOFF.h create mode 100644 src/CompilerKit/ck-osx-san.json create mode 100644 src/CompilerKit/ck-osx.json create mode 100644 src/CompilerKit/ck-posix.json create mode 100644 src/CompilerKit/impl/32x0.h create mode 100644 src/CompilerKit/impl/64x0.h create mode 100644 src/CompilerKit/impl/Aarch64.h create mode 100644 src/CompilerKit/impl/PowerPC.h create mode 100644 src/CompilerKit/impl/X64.h create mode 100644 src/CompilerKit/src/AssemblyFactory.cc create mode 100644 src/CompilerKit/src/Backend/Assembler32x0.cc create mode 100644 src/CompilerKit/src/Backend/Assembler64x0.cc create mode 100644 src/CompilerKit/src/Backend/AssemblerAMD64.cc create mode 100644 src/CompilerKit/src/Backend/AssemblerARM64.cc create mode 100644 src/CompilerKit/src/Backend/AssemblerPowerPC.cc create mode 100644 src/CompilerKit/src/Frontend.cc create mode 100644 src/CompilerKit/src/Frontend/CCompiler64x0.cc create mode 100644 src/CompilerKit/src/Frontend/CCompilerARM64.cc create mode 100644 src/CompilerKit/src/Frontend/CCompilerPower64.cc create mode 100644 src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc create mode 100644 src/CompilerKit/src/Linker/DynamicLinker64PEF.cc create mode 100644 src/CompilerKit/src/Macro/CPlusPlusPreprocessor.cc create mode 100644 src/CompilerKit/src/StringKit.cc create mode 100644 src/CompilerKit/utils/AsmUtils.h create mode 100644 src/CompilerKit/utils/CompilerUtils.h create mode 100644 src/CompilerKit/utils/DylibHelpers.h create mode 100644 src/DebuggerKit/CommonCLI.inl create mode 100644 src/DebuggerKit/DebuggerContract.h create mode 100644 src/DebuggerKit/NeKernelContract.h create mode 100644 src/DebuggerKit/POSIXMachContract.h create mode 100644 src/DebuggerKit/Platform.h create mode 100644 src/DebuggerKit/Version.h create mode 100644 src/DebuggerKit/dk-nekernel.json create mode 100644 src/DebuggerKit/dk-osx.json create mode 100644 src/DebuggerKit/src/NeKernelContract.cc create mode 100644 src/DebuggerKit/src/NeKernelContractCLI.cc create mode 100644 src/DebuggerKit/src/POSIXMachContractCLI.cc create mode 100644 src/LibC++/.gitignore create mode 100644 src/LibC++/__abi+unreachable.cc create mode 100644 src/LibC++/__abi.h create mode 100644 src/LibC++/__power64.inc create mode 100644 src/LibC++/base_alloc.h create mode 100644 src/LibC++/base_exception.h create mode 100644 src/LibC++/base_math.h create mode 100644 src/LibC++/base_process.h create mode 100644 src/LibC++/defines.h create mode 100644 src/LibC++/filesystem.h create mode 100755 src/LibC++/make_hdrs.sh create mode 100644 src/LibC++/new.cc create mode 100644 src/LibC++/new.h create mode 100644 src/LibC++/utility.h create mode 100644 src/ThirdParty/Dialogs.h (limited to 'src') diff --git a/src/.keep b/src/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/CompilerKit/AE.h b/src/CompilerKit/AE.h new file mode 100644 index 0000000..7b6d9b4 --- /dev/null +++ b/src/CompilerKit/AE.h @@ -0,0 +1,136 @@ +/* + * ======================================================== + * + * CompilerKit + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +#ifndef _NECTI_AE_H_ +#define _NECTI_AE_H_ + +#include + +#define kAEVer (0x0121) + +#define kAEMag0 'A' +#define kAEMag1 'E' + +#define kAESymbolLen (255) +#define kAEPad (8) +#define kAEMagLen (2) +#define kAENullType (0x00) + +/// @author Amlal El Mahrouss + +/// @brief +// Advanced Executable File Format for ld64. +// Reloctable by offset is the default strategy. +// You can also relocate at runtime but that's up to the operating system loader. + +namespace CompilerKit { +// @brief Advanced Executable Header +// One thing to keep in mind. +// This object format, is reloctable. +typedef struct AEHeader final { + Char fMagic[kAEMagLen] = {}; + UInt16 fVersion{kAEVer}; + Char fArch{}; + Char fSubArch{}; + SizeType fCount{}; + Char fSize{}; + SizeType fStartCode{}; + SizeType fCodeSize{}; + Char fPad[kAEPad] = {}; +} PACKED AEHeader, *AEHeaderPtr; + +// @brief Advanced Executable Record. +// Could be data, code or bss. +// fKind must be filled with PEF fields. + +typedef struct AERecordHeader final { + Char fName[kAESymbolLen]; + SizeType fKind; + SizeType fSize; + SizeType fFlags; + UIntPtr fOffset; + Char fPad[kAEPad]; +} PACKED AERecordHeader, *AERecordHeaderPtr; + +enum { + kKindRelocationByOffset = 0x23f, + kKindRelocationAtRuntime = 0x34f, +}; +} // namespace CompilerKit + +// provide operator<< for AE + +inline std::ofstream& operator<<(std::ofstream& fp, CompilerKit::AEHeader& container) { + fp.write((char*) &container, sizeof(CompilerKit::AEHeader)); + + return fp; +} + +inline std::ofstream& operator<<(std::ofstream& fp, CompilerKit::AERecordHeader& container) { + fp.write((char*) &container, sizeof(CompilerKit::AERecordHeader)); + + return fp; +} + +inline std::ifstream& operator>>(std::ifstream& fp, CompilerKit::AEHeader& container) { + fp.read((char*) &container, sizeof(CompilerKit::AEHeader)); + return fp; +} + +inline std::ifstream& operator>>(std::ifstream& fp, CompilerKit::AERecordHeader& container) { + fp.read((char*) &container, sizeof(CompilerKit::AERecordHeader)); + return fp; +} + +namespace CompilerKit::Utils { +/** + * @brief AE Reader protocol + * + */ +class AEReadableProtocol final { + public: + std::ifstream file_pointer_; + + public: + explicit AEReadableProtocol() = default; + ~AEReadableProtocol() = default; + + NECTI_COPY_DELETE(AEReadableProtocol); + + /** + * @brief Read AE Record headers. + * + * @param raw the containing buffer + * @param sz it's size (1 = one AERecordHeader, 2 two AERecordHeader(s)) + * @return AERecordHeaderPtr + */ + AERecordHeaderPtr Read(char* raw, std::size_t sz) { + if (!raw) return nullptr; + + return this->Read_(raw, sz * sizeof(AERecordHeader)); + } + + private: + /** + * @brief Implementation of Read for raw classes. + * + * @tparam TypeClass The class to read. + * @param raw the buffer + * @param sz the size + * @return TypeClass* the returning class. + */ + template + TypeClass* Read_(char* raw, std::size_t sz) { + file_pointer_.read(raw, std::streamsize(sz)); + return reinterpret_cast(raw); + } +}; +} // namespace CompilerKit::Utils + +#endif /* ifndef _NECTI_AE_H_ */ \ No newline at end of file diff --git a/src/CompilerKit/Compiler.h b/src/CompilerKit/Compiler.h new file mode 100644 index 0000000..1f24370 --- /dev/null +++ b/src/CompilerKit/Compiler.h @@ -0,0 +1,222 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include + +#define CK_ASSEMBLY_INTERFACE : public ::CompilerKit::AssemblyInterface +#define CK_ENCODER : public ::CompilerKit::EncoderInterface + +namespace CompilerKit { +class AssemblyFactory; +class AssemblyInterface; + +/// =========================================================== /// +/// @brief Simple assembly factory +/// =========================================================== /// +class AssemblyFactory final { + public: + explicit AssemblyFactory() = default; + ~AssemblyFactory() = default; + + NECTI_COPY_DEFAULT(AssemblyFactory); + + public: + enum { + kArchInvalid = 0, + kArchAMD64 = 100, + kArch32x0, + kArch64x0, + kArchRISCV, + kArchPowerPC, + kArchAARCH64, + kArchUnknown, + kArchCount = kArchUnknown - kArchAMD64, + }; + + Int32 Compile(std::string sourceFile, const Int32& arch) noexcept; + + void Mount(AssemblyInterface* mountPtr) noexcept; + AssemblyInterface* Unmount() noexcept; + + private: + AssemblyInterface* fMounted{nullptr}; +}; + +/// =========================================================== /// +/// @brief Assembly to binary generator class. +/// @note This interface creates according to the CPU target of the child class. +/// =========================================================== /// +class AssemblyInterface { + public: + explicit AssemblyInterface() = default; + virtual ~AssemblyInterface() = default; + + NECTI_COPY_DEFAULT(AssemblyInterface); + + virtual UInt32 Arch() noexcept { return AssemblyFactory::kArchAMD64; } + + /// =========================================================== /// + /// @brief compile to object file. + /// @note Example C++ -> MASM -> AE object. + /// =========================================================== /// + virtual Int32 CompileToFormat(std::string src, Int32 arch) = 0; +}; + +/// =========================================================== /// +/// @brief Number casting unions for different sizes. +/// =========================================================== /// +union NumberCastBase { + NumberCastBase() = default; + ~NumberCastBase() = default; +}; + +union NumberCast64 final { + NumberCast64() = default; + explicit NumberCast64(UInt64 raw) : raw(raw) {} + + ~NumberCast64() { raw = 0; } + + Char number[8]; + UInt64 raw; +}; + +union NumberCast32 final { + NumberCast32() = default; + explicit NumberCast32(UInt32 raw) : raw(raw) {} + + ~NumberCast32() { raw = 0; } + + Char number[4]; + UInt32 raw; +}; + +union NumberCast16 final { + NumberCast16() = default; + explicit NumberCast16(UInt16 raw) : raw(raw) {} + + ~NumberCast16() { raw = 0; } + + Char number[2]; + UInt16 raw; +}; + +union NumberCast8 final { + NumberCast8() = default; + explicit NumberCast8(UInt8 raw) : raw(raw) {} + + ~NumberCast8() { raw = 0; } + + Char number; + UInt8 raw; +}; + +/// =========================================================== /// +/// @brief Assembly encoder interface. +/// =========================================================== /// +class EncoderInterface { + public: + explicit EncoderInterface() = default; + virtual ~EncoderInterface() = default; + + NECTI_COPY_DEFAULT(EncoderInterface); + + virtual std::string CheckLine(std::string line, std::string file) = 0; + virtual bool WriteLine(std::string line, std::string file) = 0; + virtual bool WriteNumber(const std::size_t& pos, std::string& from_what) = 0; +}; + +/// =========================================================== /// +/// @brief Different architecture encoders. +/// =========================================================== /// + +#ifdef __ASM_NEED_AMD64__ + +class EncoderAMD64 final : public EncoderInterface { + public: + explicit EncoderAMD64() = default; + ~EncoderAMD64() override = default; + + NECTI_COPY_DEFAULT(EncoderAMD64); + + virtual std::string CheckLine(std::string line, std::string file) override; + virtual bool WriteLine(std::string line, std::string file) override; + virtual bool WriteNumber(const std::size_t& pos, std::string& from_what) override; + + virtual bool WriteNumber16(const std::size_t& pos, std::string& from_what); + virtual bool WriteNumber32(const std::size_t& pos, std::string& from_what); + virtual bool WriteNumber8(const std::size_t& pos, std::string& from_what); +}; + +#endif // __ASM_NEED_AMD64__ + +#ifdef __ASM_NEED_ARM64__ + +class EncoderARM64 final : public EncoderInterface { + public: + explicit EncoderARM64() = default; + ~EncoderARM64() override = default; + + NECTI_COPY_DEFAULT(EncoderARM64); + + virtual std::string CheckLine(std::string line, std::string file) override; + virtual bool WriteLine(std::string line, std::string file) override; + virtual bool WriteNumber(const std::size_t& pos, std::string& from_what) override; +}; + +#endif // __ASM_NEED_ARM64__ + +#ifdef __ASM_NEED_64x0__ + +class Encoder64x0 final : public EncoderInterface { + public: + explicit Encoder64x0() = default; + ~Encoder64x0() override = default; + + NECTI_COPY_DEFAULT(Encoder64x0); + + virtual std::string CheckLine(std::string line, std::string file) override; + virtual bool WriteLine(std::string line, std::string file) override; + virtual bool WriteNumber(const std::size_t& pos, std::string& from_what) override; +}; + +#endif // __ASM_NEED_64x0__ + +#ifdef __ASM_NEED_32x0__ + +class Encoder32x0 final : public EncoderInterface { + public: + explicit Encoder32x0() = default; + ~Encoder32x0() override = default; + + NECTI_COPY_DEFAULT(Encoder32x0); + + virtual std::string CheckLine(std::string line, std::string file) override; + virtual bool WriteLine(std::string line, std::string file) override; + virtual bool WriteNumber(const std::size_t& pos, std::string& from_what) override; +}; + +#endif // __ASM_NEED_32x0__ + +#ifdef __ASM_NEED_PPC__ + +class EncoderPowerPC final : public EncoderInterface { + public: + explicit EncoderPowerPC() = default; + ~EncoderPowerPC() override = default; + + NECTI_COPY_DEFAULT(EncoderPowerPC); + + virtual std::string CheckLine(std::string line, std::string file) override; + virtual bool WriteLine(std::string line, std::string file) override; + virtual bool WriteNumber(const std::size_t& pos, std::string& from_what) override; +}; + +#endif // __ASM_NEED_32x0__ +} // namespace CompilerKit diff --git a/src/CompilerKit/Defines.h b/src/CompilerKit/Defines.h new file mode 100644 index 0000000..d036767 --- /dev/null +++ b/src/CompilerKit/Defines.h @@ -0,0 +1,173 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#ifndef __NECTI_DEFINES_H__ +#define __NECTI_DEFINES_H__ + +/// =========================================================== /// +/// @file Defines.h +/// @author Amlal El Mahrouss +/// @brief Basic defines and types for CompilerKit. +/// =========================================================== /// + +#ifndef Yes +#define Yes true +#endif // ifndef Yes + +#ifndef No +#define No false +#endif // ifndef No + +#ifndef YES +#define YES true +#endif // ifndef YES + +#ifndef NO +#define NO false +#endif // ifndef NO + +#define SizeType size_t + +#define VoidPtr void* +#define voidPtr VoidPtr + +#define UIntPtr uintptr_t + +#define Int64 int64_t +#define UInt64 uint64_t + +#define Int32 int +#define UInt32 unsigned + +#define Bool bool + +#define Int16 int16_t +#define UInt16 uint16_t + +#define Int8 int8_t +#define UInt8 uint8_t + +#define Char char +#define Boolean bool + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define nullPtr std::nullptr_t + +#define MUST_PASS(E) assert(E) + +#ifndef __FORCE_STRLEN +#define __FORCE_STRLEN 1 + +#define string_length(len) strlen(len) +#endif + +#ifndef __FORCE_MEMCPY +#define __FORCE_MEMCPY 1 + +#define rt_copy_memory(dst, src, len) memcpy(dst, src, len) +#endif + +#define ATTRIBUTE(X) __attribute__((X)) +#define PACKED ATTRIBUTE(packed) + +#define kObjectFileExt ".obj" +#define kBinaryFileExt ".bin" + +#define kAsmFileExts \ + { ".64x", ".32x", ".masm", ".s", ".S", ".asm", ".x64" } + +#define kAsmFileExtsMax (7U) + +#define NECTI_MODULE(name) extern "C" int name(int argc, char** argv) + +#ifdef MSVC +#pragma scalar_storage_order big - endian +#endif // ifdef MSVC + +#define NECTI_COPY_DELETE(KLASS) \ + KLASS& operator=(const KLASS&) = delete; \ + KLASS(const KLASS&) = delete; + +#define NECTI_COPY_DEFAULT(KLASS) \ + KLASS& operator=(const KLASS&) = default; \ + KLASS(const KLASS&) = default; + +#define NECTI_MOVE_DELETE(KLASS) \ + KLASS& operator=(KLASS&&) = delete; \ + KLASS(KLASS&&) = delete; + +#define NECTI_MOVE_DEFAULT(KLASS) \ + KLASS& operator=(KLASS&&) = default; \ + KLASS(KLASS&&) = default; + +#define CK_IMPORT_C extern "C" +#define CK_IMPORT extern +namespace CompilerKit { +inline constexpr int kBaseYear = 1900; + +typedef std::string STLString; + +inline STLString current_date() noexcept { + auto time_data = time(nullptr); + auto time_struct = gmtime(&time_data); + + STLString fmt = std::to_string(kBaseYear + time_struct->tm_year); + + fmt += "-"; + fmt += std::to_string(time_struct->tm_mon + 1); + fmt += "-"; + fmt += std::to_string(time_struct->tm_mday); + + return fmt; +} + +inline bool to_str(Char* str, Int32 limit, Int32 base) noexcept { + if (limit == 0) return false; + + Int32 copy_limit = limit; + Int32 cnt = 0; + Int32 ret = base; + + while (limit != 1) { + ret = ret % 10; + str[cnt] = ret; + + ++cnt; + --limit; + --ret; + } + + str[copy_limit] = '\0'; + return true; +} + +inline bool install_signal(Int32 signal, void (*handler)(int)) noexcept { + if (handler == nullptr) return false; + + if (::signal(signal, handler) == SIG_ERR) { + return false; + } + + return true; +} +} // namespace CompilerKit + +#endif /* ifndef __NECTI_DEFINES_H__ */ diff --git a/src/CompilerKit/ErrorID.h b/src/CompilerKit/ErrorID.h new file mode 100644 index 0000000..35039ab --- /dev/null +++ b/src/CompilerKit/ErrorID.h @@ -0,0 +1,29 @@ +/* + * ======================================================== + * + * CompilerKit + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +#pragma once + +#include + +/// =========================================================== /// +/// @file ErrorID.h +/// @author Amlal El Mahrouss +/// @brief Error IDs for CompilerKit. +/// =========================================================== /// + +#define NECTI_SUCCESS 0 +#define NECTI_EXEC_ERROR -30 +#define NECTI_FILE_NOT_FOUND -31 +#define NECTI_DIR_NOT_FOUND -32 +#define NECTI_FILE_EXISTS -33 +#define NECTI_TOO_LONG -34 +#define NECTI_INVALID_DATA -35 +#define NECTI_UNIMPLEMENTED -36 +#define NECTI_FAT_ERROR -37 +#define NECTI_INVALID_ARCH -38 diff --git a/src/CompilerKit/ErrorOr.h b/src/CompilerKit/ErrorOr.h new file mode 100644 index 0000000..ac42c19 --- /dev/null +++ b/src/CompilerKit/ErrorOr.h @@ -0,0 +1,55 @@ +/* + * ======================================================== + * + * CompilerKit + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +#pragma once + +/// =========================================================== /// +/// @file ErrorOr.h +/// @author Amlal El Mahrouss +/// @brief ErrorOr for CompilerKit. +/// =========================================================== /// + +#include +#include +#include +#include + +namespace CompilerKit { +using ErrorT = Int32; + +template +class ErrorOr final { + public: + ErrorOr() = default; + ~ErrorOr() = default; + + public: + explicit ErrorOr(ErrorT err) : mId(err) {} + explicit ErrorOr(nullPtr null) {} + explicit ErrorOr(T klass) : mRef(klass) {} + + ErrorOr& operator=(const ErrorOr&) = default; + ErrorOr(const ErrorOr&) = default; + + Ref Leak() { return mRef; } + + ErrorT Error() { return mId; } + + Bool HasError() { return mId != NECTI_SUCCESS; } + + explicit operator bool() { return mRef; } + + private: + Ref mRef; + ErrorT mId{0}; +}; + +using ErrorOrAny = ErrorOr; +using ErrorOrString = ErrorOr; +} // namespace CompilerKit diff --git a/src/CompilerKit/Frontend.h b/src/CompilerKit/Frontend.h new file mode 100644 index 0000000..df70048 --- /dev/null +++ b/src/CompilerKit/Frontend.h @@ -0,0 +1,138 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include + +#define CK_COMPILER_FRONTEND : public ::CompilerKit::CompilerFrontendInterface + +namespace CompilerKit { +inline static auto kInvalidFrontend = "(null)"; + +struct SyntaxLeafList; +struct SyntaxLeafList; +struct CompilerKeyword; + +/// =========================================================== /// +/// @note we want to do that to separate keywords. +/// =========================================================== /// + +enum KeywordKind { + kKeywordKindNamespace, + kKeywordKindFunctionStart, + kKeywordKindFunctionEnd, + kKeywordKindVariable, + kKeywordKindVariablePtr, + kKeywordKindType, + kKeywordKindTypePtr, + kKeywordKindExpressionBegin, + kKeywordKindExpressionEnd, + kKeywordKindArgSeparator, + kKeywordKindBodyStart, + kKeywordKindBodyEnd, + kKeywordKindClass, + kKeywordKindPtrAccess, + kKeywordKindAccess, + kKeywordKindIf, + kKeywordKindElse, + kKeywordKindElseIf, + kKeywordKindVariableAssign, + kKeywordKindVariableDec, + kKeywordKindVariableInc, + kKeywordKindConstant, + kKeywordKindTypedef, + kKeywordKindEndInstr, + kKeywordKindSpecifier, + kKeywordKindInvalid, + kKeywordKindReturn, + kKeywordKindCommentInline, + kKeywordKindCommentMultiLineStart, + kKeywordKindCommentMultiLineEnd, + kKeywordKindEq, + kKeywordKindNotEq, + kKeywordKindGreaterEq, + kKeywordKindLessEq, + kKeywordKindPtr, +}; + +/// =========================================================== /// +/// \brief Compiler keyword information struct. +/// =========================================================== /// +struct CompilerKeyword { + CompilerKeyword(STLString name, KeywordKind kind) : keyword_name(name), keyword_kind(kind) {} + + STLString keyword_name{""}; + KeywordKind keyword_kind{kKeywordKindInvalid}; +}; + +struct SyntaxLeafList final { + struct SyntaxLeaf final { + Int32 fUserType{0U}; + CompilerKeyword fUserData{"", kKeywordKindInvalid}; + + STLString fUserValue{""}; + struct SyntaxLeaf* fNext{nullptr}; + }; + + std::vector fLeafList; + SizeType fNumLeafs{0}; + + SizeType SizeOf() { return fNumLeafs; } + std::vector& Get() { return fLeafList; } + SyntaxLeaf& At(SizeType index) { return fLeafList[index]; } +}; + +/// =========================================================== /// +/// find the perfect matching word in a haystack. +/// \param haystack base string +/// \param needle the string we search for. +/// \return if we found it or not. +/// =========================================================== /// +Bool find_word(STLString haystack, STLString needle) noexcept; + +/// =========================================================== /// +/// find a word within strict conditions and returns a range of it. +/// \param haystack +/// \param needle +/// \return position of needle. +/// =========================================================== /// +SizeType find_word_range(STLString haystack, STLString needle) noexcept; + +/// =========================================================== /// +/// @brief Compiler backend, implements a frontend, such as C, C++... +/// See Toolchain, for some examples. +/// =========================================================== /// +class CompilerFrontendInterface { + public: + explicit CompilerFrontendInterface() = default; + virtual ~CompilerFrontendInterface() = default; + + NECTI_COPY_DEFAULT(CompilerFrontendInterface); + + /// =========================================================== /// + // NOTE: cast this to your user defined ast. + /// =========================================================== /// + typedef VoidPtr AstType; + + /// =========================================================== /// + //! @brief Compile a syntax tree ouf of the text. + //! Also takes the source file name for metadata. + /// =========================================================== /// + + virtual CompilerKit::SyntaxLeafList::SyntaxLeaf Compile(std::string text, std::string file) = 0; + + /// =========================================================== /// + //! @brief What language are we dealing with? + /// =========================================================== /// + virtual const char* Language() { return kInvalidFrontend; } + + /// =========================================================== /// + /// @brief Checks if language is a valid frontend. + /// =========================================================== /// + virtual bool IsValid() { return strcmp(this->Language(), kInvalidFrontend) > 0; } +}; +} // namespace CompilerKit diff --git a/src/CompilerKit/Macros.h b/src/CompilerKit/Macros.h new file mode 100644 index 0000000..6cef758 --- /dev/null +++ b/src/CompilerKit/Macros.h @@ -0,0 +1,28 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +/// @brief provide support for Macros.h header. + +#ifndef _NECTI_MACROS_H_ +#define _NECTI_MACROS_H_ + +#define NECTI_COPY_DELETE(KLASS) \ + KLASS& operator=(const KLASS&) = delete; \ + KLASS(const KLASS&) = delete; + +#define NECTI_COPY_DEFAULT(KLASS) \ + KLASS& operator=(const KLASS&) = default; \ + KLASS(const KLASS&) = default; + +#define NECTI_MOVE_DELETE(KLASS) \ + KLASS& operator=(KLASS&&) = delete; \ + KLASS(KLASS&&) = delete; + +#define NECTI_MOVE_DEFAULT(KLASS) \ + KLASS& operator=(KLASS&&) = default; \ + KLASS(KLASS&&) = default; + +#endif /* ifndef _NECTI_MACROS_H_ */ diff --git a/src/CompilerKit/PEF.h b/src/CompilerKit/PEF.h new file mode 100644 index 0000000..edf8f5d --- /dev/null +++ b/src/CompilerKit/PEF.h @@ -0,0 +1,137 @@ +/* ========================================= + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include + +// @file PEF.h +// @brief Preferred Executable Format + +#define kPefMagic "Open" +#define kPefMagicFat "nepO" + +#define kPefExt ".exec" +#define kPefDylibExt ".dylib" +#define kPefLibExt ".lib" +#define kPefObjectExt ".obj" +#define kPefDebugExt ".dbg" +#define kPefDriverExt ".sys" + +#define kPefZero128 ".zero128" +#define kPefCode128 ".code128" +#define kPefData128 ".data128" + +#define kPefZero64 ".zero64" +#define kPefCode64 ".code64" +#define kPefData64 ".data64" + +/* @note counting the \0 at the end */ +#define kPefMagicLen (5) + +#define kPefVersion (0x0500) +#define kPefNameLen (255) + +#define kPefBaseOrigin (0x40000000) + +/* @note this doesn't have to be __ImageStart only, any C initialization stub will do. */ +#define kPefStart "__ImageStart" + +namespace CompilerKit { +/* @brief Architecture type. */ +enum { + kPefArchIntel86S, + kPefArchAMD64, + kPefArchRISCV, + kPefArch64000, /* Open64x0 RISC architecture. */ + kPefArch32000, + kPefArchPowerPC, /* 64-bit POWER architecture. */ + kPefArchARM64, + kPefArchCount = (kPefArchARM64 - kPefArchIntel86S) + 1, + kPefArchInvalid = 0xFF, +}; + +/* @brief Architecture vendor. */ +enum { + kPefSubArchGeneric = 0, + kPefSubArchAMD = 200, + kPefSubArchIntel, + kPefSubArchARM, + kPefSubArchIBM, +}; + +enum { + kPefKindInvalid = 0, + kPefKindExec = 1, /* .exec */ + kPefKindDylib = 2, /* .dylib */ + kPefKindObject = 4, /* .obj */ + kPefKindDebug = 5, /* .dbg */ + kPefKindDriver = 6, + kPefKindCount, +}; + +/* PEF container information */ +typedef struct PEFContainer final { + Char Magic[kPefMagicLen]; + UInt32 Linker; /* Linker used to link executable */ + UInt32 Version; + UInt32 Kind; + UInt32 Abi; + UInt32 Cpu; + UInt32 SubCpu; /* Cpu specific information */ + UIntPtr Start; /* Origin of code */ + SizeType HdrSz; /* Size of header */ + SizeType Count; /* container header count */ + UInt32 Checksum; /* Whole binary checksum */ +} PACKED PEFContainer, *PEFContainerPtr; + +/* First PEFCommandHeader starts after PEFContainer */ +/* Last container is __exec_end */ + +/* PEF executable section and commands. */ + +/* @brief Command Header, a la Mach-O, designed with FAT binaries and virtual memory in mind. */ +typedef struct PEFCommandHeader final { + Char Name[kPefNameLen]; /* container name */ + UInt32 Cpu; /* container cpu */ + UInt32 SubCpu; /* container sub-cpu */ + UInt32 Flags; /* container flags */ + UInt16 Kind; /* container kind */ + UIntPtr Offset; /* File offset */ + SizeType OffsetSize; + UIntPtr VirtualAddress; /* Virtual Address */ + SizeType VirtualSize; /* Virtual Size */ +} PACKED PEFCommandHeader, *PEFCommandHeaderPtr; + +enum { + kPefInvalid = 0x0, + kPefCode = 0xC, + kPefData = 0xD, + kPefZero = 0xE, + kPefLinkerID = 0x1, + kPefCount = 4, +}; +} // namespace CompilerKit + +inline std::ofstream& operator<<(std::ofstream& fp, CompilerKit::PEFContainer& container) { + fp.write((char*) &container, sizeof(CompilerKit::PEFContainer)); + return fp; +} + +inline std::ofstream& operator<<(std::ofstream& fp, CompilerKit::PEFCommandHeader& container) { + fp.write((char*) &container, sizeof(CompilerKit::PEFCommandHeader)); + return fp; +} + +inline std::ifstream& operator>>(std::ifstream& fp, CompilerKit::PEFContainer& container) { + fp.read((char*) &container, sizeof(CompilerKit::PEFContainer)); + return fp; +} + +inline std::ifstream& operator>>(std::ifstream& fp, CompilerKit::PEFCommandHeader& container) { + fp.read((char*) &container, sizeof(CompilerKit::PEFCommandHeader)); + return fp; +} diff --git a/src/CompilerKit/Ref.h b/src/CompilerKit/Ref.h new file mode 100644 index 0000000..66ed60f --- /dev/null +++ b/src/CompilerKit/Ref.h @@ -0,0 +1,77 @@ + +/* + * ======================================================== + * + * CompilerKit + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +#pragma once + +#include + +namespace CompilerKit { +/// @author Amlal El Mahrouss +/// @brief Reference holder class, refers to a pointer of data in static memory. +template +class Ref final { + public: + explicit Ref() = default; + + ~Ref() { + if (m_Strong) { + MUST_PASS(m_Class); + if (m_Class) delete m_Class; + m_Class = nullptr; + } + } + + NECTI_COPY_DEFAULT(Ref); + + public: + explicit Ref(T* cls, const Bool& strong = false) : m_Class(cls), m_Strong(strong) {} + + Ref& operator=(T ref) { + *m_Class = ref; + return *this; + } + + public: + T* operator->() const { return m_Class; } + + T& Leak() { return *m_Class; } + + T operator*() { return *m_Class; } + + Bool IsStrong() const { return m_Strong; } + + explicit operator bool() { return *m_Class; } + + private: + T* m_Class{nullptr}; + Bool m_Strong{false}; +}; + +// @author Amlal El Mahrouss +// @brief Non null Reference holder class, refers to a pointer of data in static memory. +template +class NonNullRef final { + public: + explicit NonNullRef() = delete; + + explicit NonNullRef(T* ref) : m_Ref(ref, true) {} + + Ref& operator->() { + MUST_PASS(m_Ref); + return m_Ref; + } + + NonNullRef& operator=(const NonNullRef& ref) = delete; + NonNullRef(const NonNullRef& ref) = default; + + private: + Ref m_Ref{nullptr}; +}; +} // namespace CompilerKit diff --git a/src/CompilerKit/StringKit.h b/src/CompilerKit/StringKit.h new file mode 100644 index 0000000..eb7ccc3 --- /dev/null +++ b/src/CompilerKit/StringKit.h @@ -0,0 +1,94 @@ +/* + * ======================================================== + * + * CompilerKit + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +#ifndef __NECTI_STRINGKIT__ +#define __NECTI_STRINGKIT__ + +#include +#include + +/// =========================================================== /// +/// @file StringKit.h +/// @author Amlal El Mahrouss +/// @brief StringKit for CompilerKit. +/// =========================================================== /// + +namespace CompilerKit { +class StringBuilder; +class BasicString; + +/** + * @brief BasicString class, contains a C string and manages it. + * @note No need to manage it it's getting deleted by default. + */ + +class BasicString final { + public: + explicit BasicString() = delete; + + explicit BasicString(SizeType Sz) noexcept : m_Sz(Sz) { + m_Data = new Char[Sz]; + assert(m_Data); + } + + ~BasicString() noexcept { + if (m_Data) { + memset(m_Data, 0, m_Sz); + delete[] m_Data; + + m_Data = nullptr; + } + } + + NECTI_COPY_DEFAULT(BasicString); + + Char* Data(); + const Char* CData() const; + SizeType Length() const; + + bool operator==(const Char* rhs) const; + bool operator!=(const Char* rhs) const; + + bool operator==(const BasicString& rhs) const; + bool operator!=(const BasicString& rhs) const; + + BasicString& operator+=(const Char* rhs); + BasicString& operator+=(const Char rhs); + BasicString& operator+=(const BasicString& rhs); + + explicit operator bool() { return m_Data && m_Data[0] != 0; } + + bool operator!() { return !m_Data || m_Data[0] == 0; } + + private: + Char* m_Data{nullptr}; + SizeType m_Sz{0}; + SizeType m_Cur{0}; + + friend class StringBuilder; +}; + +/** + * @brief StringBuilder class + * @note These results shall call be delete[] after they're used. + */ +struct StringBuilder final { + static BasicString Construct(const Char* data); + static BasicString FromInt(const char* fmt, int n); + static BasicString FromBool(const char* fmt, bool n); + static BasicString Format(const char* fmt, const char* from); + static Bool Equals(const char* lhs, const char* rhs); +}; + +using BasicStringOr = ErrorOr; +using BasicStringPtr = BasicString*; +using BasicStringRef = Ref; +} // namespace CompilerKit + +#endif /* ifndef __NECTI_STRINGKIT__ */ diff --git a/src/CompilerKit/UUID.h b/src/CompilerKit/UUID.h new file mode 100644 index 0000000..98733fd --- /dev/null +++ b/src/CompilerKit/UUID.h @@ -0,0 +1,826 @@ +#ifndef STDUUID_H +#define STDUUID_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus + +#if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#define LIBUUID_CPP20_OR_GREATER +#endif + +#endif + +#ifdef LIBUUID_CPP20_OR_GREATER +#include +#else +#include +#endif + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef UUID_SYSTEM_GENERATOR +#include +#endif + +#ifdef UUID_TIME_GENERATOR +#include +#pragma comment(lib, "IPHLPAPI.lib") +#endif + +#elif defined(__linux__) || defined(__unix__) + +#ifdef UUID_SYSTEM_GENERATOR +#include +#endif + +#elif defined(__APPLE__) + +#ifdef UUID_SYSTEM_GENERATOR +#include +#endif + +#endif + +namespace uuids { +#ifdef __cpp_lib_span +template +using span = std::span; +#else +template +using span = gsl::span; +#endif + +namespace Detail { + template + [[nodiscard]] constexpr inline unsigned char hex2char(TChar const ch) noexcept { + if (ch >= static_cast('0') && ch <= static_cast('9')) + return static_cast(ch - static_cast('0')); + if (ch >= static_cast('a') && ch <= static_cast('f')) + return static_cast(10 + ch - static_cast('a')); + if (ch >= static_cast('A') && ch <= static_cast('F')) + return static_cast(10 + ch - static_cast('A')); + return 0; + } + + template + [[nodiscard]] constexpr std::basic_string_view to_string_view(TChar const* str) noexcept { + return str; + } + + template + [[nodiscard]] constexpr std::basic_string_view + to_string_view(StringType const& str) noexcept { + return str; + } + + class sha1 { + public: + using digest32_t = uint32_t[5]; + using digest8_t = uint8_t[20]; + + static constexpr unsigned int block_bytes = 64; + + [[nodiscard]] inline static uint32_t left_rotate(uint32_t value, size_t const count) noexcept { + return (value << count) ^ (value >> (32 - count)); + } + + sha1() { reset(); } + + void reset() noexcept { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + m_blockByteIndex = 0; + m_byteCount = 0; + } + + void process_byte(uint8_t octet) { + this->m_block[this->m_blockByteIndex++] = octet; + ++this->m_byteCount; + if (m_blockByteIndex == block_bytes) { + this->m_blockByteIndex = 0; + process_block(); + } + } + + void process_block(void const* const start, void const* const end) { + const uint8_t* begin = static_cast(start); + const uint8_t* finish = static_cast(end); + while (begin != finish) { + process_byte(*begin); + begin++; + } + } + + void process_bytes(void const* const data, size_t const len) { + const uint8_t* block = static_cast(data); + process_block(block, block + len); + } + + uint32_t const* get_digest(digest32_t digest) { + size_t const bitCount = this->m_byteCount * 8; + process_byte(0x80); + if (this->m_blockByteIndex > 56) { + while (m_blockByteIndex != 0) { + process_byte(0); + } + while (m_blockByteIndex < 56) { + process_byte(0); + } + } else { + while (m_blockByteIndex < 56) { + process_byte(0); + } + } + process_byte(0); + process_byte(0); + process_byte(0); + process_byte(0); + process_byte(static_cast((bitCount >> 24) & 0xFF)); + process_byte(static_cast((bitCount >> 16) & 0xFF)); + process_byte(static_cast((bitCount >> 8) & 0xFF)); + process_byte(static_cast((bitCount) &0xFF)); + + memcpy(digest, m_digest, 5 * sizeof(uint32_t)); + return digest; + } + + uint8_t const* get_digest_bytes(digest8_t digest) { + digest32_t d32; + get_digest(d32); + size_t di = 0; + digest[di++] = static_cast(d32[0] >> 24); + digest[di++] = static_cast(d32[0] >> 16); + digest[di++] = static_cast(d32[0] >> 8); + digest[di++] = static_cast(d32[0] >> 0); + + digest[di++] = static_cast(d32[1] >> 24); + digest[di++] = static_cast(d32[1] >> 16); + digest[di++] = static_cast(d32[1] >> 8); + digest[di++] = static_cast(d32[1] >> 0); + + digest[di++] = static_cast(d32[2] >> 24); + digest[di++] = static_cast(d32[2] >> 16); + digest[di++] = static_cast(d32[2] >> 8); + digest[di++] = static_cast(d32[2] >> 0); + + digest[di++] = static_cast(d32[3] >> 24); + digest[di++] = static_cast(d32[3] >> 16); + digest[di++] = static_cast(d32[3] >> 8); + digest[di++] = static_cast(d32[3] >> 0); + + digest[di++] = static_cast(d32[4] >> 24); + digest[di++] = static_cast(d32[4] >> 16); + digest[di++] = static_cast(d32[4] >> 8); + digest[di++] = static_cast(d32[4] >> 0); + + return digest; + } + + private: + void process_block() { + uint32_t w[80]; + for (size_t i = 0; i < 16; i++) { + w[i] = static_cast(m_block[i * 4 + 0] << 24); + w[i] |= static_cast(m_block[i * 4 + 1] << 16); + w[i] |= static_cast(m_block[i * 4 + 2] << 8); + w[i] |= static_cast(m_block[i * 4 + 3]); + } + for (size_t i = 16; i < 80; i++) { + w[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); + } + + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) { + uint32_t f = 0; + uint32_t k = 0; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = left_rotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = left_rotate(b, 30); + b = a; + a = temp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } + + private: + digest32_t m_digest; + uint8_t m_block[64]; + size_t m_blockByteIndex; + size_t m_byteCount; + }; + + template + inline constexpr CharT empty_guid[37] = "00000000-0000-0000-0000-000000000000"; + + template <> + inline constexpr wchar_t empty_guid[37] = L"00000000-0000-0000-0000-000000000000"; + + template + inline constexpr CharT guid_encoder[17] = "0123456789abcdef"; + + template <> + inline constexpr wchar_t guid_encoder[17] = L"0123456789abcdef"; +} // namespace Detail + +// -------------------------------------------------------------------------------------------------------------------------- +// UUID format https://tools.ietf.org/html/rfc4122 +// -------------------------------------------------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------------------------------------------------- +// Field NDR Data Type Octet # Note +// -------------------------------------------------------------------------------------------------------------------------- +// time_low unsigned long 0 - 3 The low field +// of the timestamp. time_mid unsigned short 4 - 5 +// The middle field of the timestamp. time_hi_and_version unsigned +// short 6 - 7 The high field of the timestamp multiplexed +// with the version number. clock_seq_hi_and_reserved unsigned small 8 +// The high field of the clock sequence multiplexed with the variant. +// clock_seq_low unsigned small 9 The low +// field of the clock sequence. node character 10 +// - 15 The spatially unique node identifier. +// -------------------------------------------------------------------------------------------------------------------------- +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | time_low | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | time_mid | time_hi_and_version | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |clk_seq_hi_res | clk_seq_low | node (0-1) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | node (2-5) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +// -------------------------------------------------------------------------------------------------------------------------- +// enumerations +// -------------------------------------------------------------------------------------------------------------------------- + +// indicated by a bit pattern in octet 8, marked with N in +// xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx +enum class uuid_variant { + // NCS backward compatibility (with the obsolete Apollo Network Computing + // System 1.5 UUID format) N bit pattern: 0xxx > the first 6 octets of the + // UUID are a 48-bit timestamp (the number of 4 microsecond units of time + // since 1 Jan 1980 UTC); > the next 2 octets are reserved; > the next octet + // is the "address family"; > the final 7 octets are a 56-bit host ID in the + // form specified by the address family + ncs, + + // RFC 4122/DCE 1.1 + // N bit pattern: 10xx + // > big-endian byte order + rfc, + + // Microsoft Corporation backward compatibility + // N bit pattern: 110x + // > little endian byte order + // > formely used in the Component Object Model (COM) library + microsoft, + + // reserved for possible future definition + // N bit pattern: 111x + reserved +}; + +// indicated by a bit pattern in octet 6, marked with M in +// xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx +enum class uuid_version { + none = 0, // only possible for nil or invalid uuids + time_based = 1, // The time-based version specified in RFC 4122 + dce_security = 2, // DCE Security version, with embedded POSIX UIDs. + name_based_md5 = 3, // The name-based version specified in RFS 4122 with MD5 hashing + random_number_based = 4, // The randomly or pseudo-randomly generated version + // specified in RFS 4122 + name_based_sha1 = 5 // The name-based version specified in RFS 4122 with SHA1 hashing +}; + +// Forward declare uuid & to_string so that we can declare to_string as a friend +// later. +class uuid; +template , + class Allocator = std::allocator> +std::basic_string to_string(uuid const& id); + +// -------------------------------------------------------------------------------------------------------------------------- +// uuid class +// -------------------------------------------------------------------------------------------------------------------------- +class uuid { + public: + using value_type = uint8_t; + + constexpr uuid() noexcept = default; + + uuid(value_type (&arr)[16]) noexcept { + std::copy(std::cbegin(arr), std::cend(arr), std::begin(data)); + } + + constexpr uuid(std::array const& arr) noexcept : data{arr} {} + + explicit uuid(span bytes) { + std::copy(std::cbegin(bytes), std::cend(bytes), std::begin(data)); + } + + template + explicit uuid(ForwardIterator first, ForwardIterator last) { + if (std::distance(first, last) == 16) std::copy(first, last, std::begin(data)); + } + + [[nodiscard]] constexpr uuid_variant variant() const noexcept { + if ((data[8] & 0x80) == 0x00) + return uuid_variant::ncs; + else if ((data[8] & 0xC0) == 0x80) + return uuid_variant::rfc; + else if ((data[8] & 0xE0) == 0xC0) + return uuid_variant::microsoft; + else + return uuid_variant::reserved; + } + + [[nodiscard]] constexpr uuid_version version() const noexcept { + if ((data[6] & 0xF0) == 0x10) + return uuid_version::time_based; + else if ((data[6] & 0xF0) == 0x20) + return uuid_version::dce_security; + else if ((data[6] & 0xF0) == 0x30) + return uuid_version::name_based_md5; + else if ((data[6] & 0xF0) == 0x40) + return uuid_version::random_number_based; + else if ((data[6] & 0xF0) == 0x50) + return uuid_version::name_based_sha1; + else + return uuid_version::none; + } + + [[nodiscard]] constexpr bool is_nil() const noexcept { + for (size_t i = 0; i < data.size(); ++i) + if (data[i] != 0) return false; + return true; + } + + void swap(uuid& other) noexcept { data.swap(other.data); } + + [[nodiscard]] inline span as_bytes() const { + return span(reinterpret_cast(data.data()), 16); + } + + template + [[nodiscard]] constexpr static bool is_valid_uuid(StringType const& in_str) noexcept { + auto str = Detail::to_string_view(in_str); + bool firstDigit = true; + size_t hasBraces = 0; + size_t index = 0; + + if (str.empty()) return false; + + if (str.front() == '{') hasBraces = 1; + if (hasBraces && str.back() != '}') return false; + + for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + if (str[i] == '-') continue; + + if (index >= 16 || !std::isxdigit(static_cast(str[i]))) { + return false; + } + + if (firstDigit) { + firstDigit = false; + } else { + index++; + firstDigit = true; + } + } + + if (index < 16) { + return false; + } + + return true; + } + + template + [[nodiscard]] constexpr static std::optional from_string( + StringType const& in_str) noexcept { + auto str = Detail::to_string_view(in_str); + bool firstDigit = true; + size_t hasBraces = 0; + size_t index = 0; + + std::array data{{0}}; + + if (str.empty()) return {}; + + if (str.front() == '{') hasBraces = 1; + if (hasBraces && str.back() != '}') return {}; + + for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + if (str[i] == '-') continue; + + if (index >= 16 || !std::isxdigit(static_cast(str[i]))) { + return {}; + } + + if (firstDigit) { + data[index] = static_cast(Detail::hex2char(str[i]) << 4); + firstDigit = false; + } else { + data[index] = static_cast(data[index] | Detail::hex2char(str[i])); + index++; + firstDigit = true; + } + } + + if (index < 16) { + return {}; + } + + return uuid{data}; + } + + private: + std::array data{{0}}; + + friend bool operator==(uuid const& lhs, uuid const& rhs) noexcept; + friend bool operator<(uuid const& lhs, uuid const& rhs) noexcept; + + template + friend std::basic_ostream& operator<<(std::basic_ostream& s, + uuid const& id); + + template + friend std::basic_string to_string(uuid const& id); + + friend std::hash; +}; + +// -------------------------------------------------------------------------------------------------------------------------- +// operators and non-member functions +// -------------------------------------------------------------------------------------------------------------------------- + +[[nodiscard]] inline bool operator==(uuid const& lhs, uuid const& rhs) noexcept { + return lhs.data == rhs.data; +} + +[[nodiscard]] inline bool operator!=(uuid const& lhs, uuid const& rhs) noexcept { + return !(lhs == rhs); +} + +[[nodiscard]] inline bool operator<(uuid const& lhs, uuid const& rhs) noexcept { + return lhs.data < rhs.data; +} + +template +[[nodiscard]] inline std::basic_string to_string(uuid const& id) { + std::basic_string uustr{Detail::empty_guid}; + + for (size_t i = 0, index = 0; i < 36; ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) { + continue; + } + uustr[i] = Detail::guid_encoder[id.data[index] >> 4 & 0x0f]; + uustr[++i] = Detail::guid_encoder[id.data[index] & 0x0f]; + index++; + } + + return uustr; +} + +template +std::basic_ostream& operator<<(std::basic_ostream& s, uuid const& id) { + s << to_string(id); + return s; +} + +inline void swap(uuids::uuid& lhs, uuids::uuid& rhs) noexcept { + lhs.swap(rhs); +} + +// -------------------------------------------------------------------------------------------------------------------------- +// namespace IDs that could be used for generating name-based uuids +// -------------------------------------------------------------------------------------------------------------------------- + +// Name string is a fully-qualified domain name +static uuid uuid_namespace_dns{{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, + 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; + +// Name string is a URL +static uuid uuid_namespace_url{{0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, + 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; + +// Name string is an ISO OID (See https://oidref.com/, +// https://en.wikipedia.org/wiki/Object_identifier) +static uuid uuid_namespace_oid{{0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, + 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; + +// Name string is an X.500 DN, in DER or a text output format (See +// https://en.wikipedia.org/wiki/X.500, +// https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One) +static uuid uuid_namespace_x500{{0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, + 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; + +// -------------------------------------------------------------------------------------------------------------------------- +// uuid generators +// -------------------------------------------------------------------------------------------------------------------------- + +#ifdef UUID_SYSTEM_GENERATOR +class uuid_system_generator { + public: + using result_type = uuid; + + uuid operator()() { +#ifdef _WIN32 + GUID newId{}; /// AMLALE: Should be zero-initialized. + HRESULT hr = ::CoCreateGuid(&newId); + + if (FAILED(hr)) { + throw std::system_error(hr, std::system_category(), "CoCreateGuid failed"); + } + + std::array bytes = {{static_cast((newId.Data1 >> 24) & 0xFF), + static_cast((newId.Data1 >> 16) & 0xFF), + static_cast((newId.Data1 >> 8) & 0xFF), + static_cast((newId.Data1) & 0xFF), + static_cast((newId.Data2 >> 8) & 0xFF), + static_cast((newId.Data2) & 0xFF), + static_cast((newId.Data3 >> 8) & 0xFF), + static_cast((newId.Data3) & 0xFF), + newId.Data4[0], newId.Data4[1], newId.Data4[2], + newId.Data4[3], newId.Data4[4], newId.Data4[5], + newId.Data4[6], newId.Data4[7]}}; + + return uuid{std::begin(bytes), std::end(bytes)}; + +#elif defined(__linux__) || defined(__unix__) + + uuid_t id; + uuid_generate(id); + + std::array bytes = {{id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], + id[9], id[10], id[11], id[12], id[13], id[14], id[15]}}; + + return uuid{std::begin(bytes), std::end(bytes)}; + +#elif defined(__APPLE__) + auto newId = CFUUIDCreate(NULL); + auto bytes = CFUUIDGetUUIDBytes(newId); + CFRelease(newId); + + std::array arrbytes = {{bytes.byte0, bytes.byte1, bytes.byte2, bytes.byte3, + bytes.byte4, bytes.byte5, bytes.byte6, bytes.byte7, + bytes.byte8, bytes.byte9, bytes.byte10, bytes.byte11, + bytes.byte12, bytes.byte13, bytes.byte14, bytes.byte15}}; + return uuid{std::begin(arrbytes), std::end(arrbytes)}; +#else + return uuid{}; +#endif + } +}; +#endif + +template +class basic_uuid_random_generator { + public: + using engine_type = UniformRandomNumberGenerator; + + explicit basic_uuid_random_generator(engine_type& gen) : generator(&gen, [](auto) {}) {} + explicit basic_uuid_random_generator(engine_type* gen) : generator(gen, [](auto) {}) {} + + [[nodiscard]] uuid operator()() { + alignas(uint32_t) uint8_t bytes[16]; + for (int i = 0; i < 16; i += 4) + *reinterpret_cast(bytes + i) = distribution(*generator); + + // variant must be 10xxxxxx + bytes[8] &= 0xBF; + bytes[8] |= 0x80; + + // version must be 0100xxxx + bytes[6] &= 0x4F; + bytes[6] |= 0x40; + + return uuid{std::begin(bytes), std::end(bytes)}; + } + + private: + std::uniform_int_distribution distribution; + std::shared_ptr generator; +}; + +using uuid_random_generator = basic_uuid_random_generator; + +class uuid_name_generator { + public: + explicit uuid_name_generator(uuid const& namespace_uuid) noexcept : nsuuid(namespace_uuid) {} + + template + [[nodiscard]] uuid operator()(StringType const& name) { + reset(); + process_characters(Detail::to_string_view(name)); + return make_uuid(); + } + + private: + void reset() { + hasher.reset(); + std::byte bytes[16]; + auto nsbytes = nsuuid.as_bytes(); + std::copy(std::cbegin(nsbytes), std::cend(nsbytes), bytes); + hasher.process_bytes(bytes, 16); + } + + template + void process_characters(std::basic_string_view const str) { + for (uint32_t c : str) { + hasher.process_byte(static_cast(c & 0xFF)); + if constexpr (!std::is_same_v) { + hasher.process_byte(static_cast((c >> 8) & 0xFF)); + hasher.process_byte(static_cast((c >> 16) & 0xFF)); + hasher.process_byte(static_cast((c >> 24) & 0xFF)); + } + } + } + + [[nodiscard]] uuid make_uuid() { + Detail::sha1::digest8_t digest; + hasher.get_digest_bytes(digest); + + // variant must be 0b10xxxxxx + digest[8] &= 0xBF; + digest[8] |= 0x80; + + // version must be 0b0101xxxx + digest[6] &= 0x5F; + digest[6] |= 0x50; + + return uuid{digest, digest + 16}; + } + + private: + uuid nsuuid; + Detail::sha1 hasher; +}; + +#ifdef UUID_TIME_GENERATOR +// !!! DO NOT USE THIS IN PRODUCTION +// this implementation is unreliable for good uuids +class uuid_time_generator { + using mac_address = std::array; + + std::optional device_address; + + [[nodiscard]] bool get_mac_address() { + if (device_address.has_value()) { + return true; + } + +#ifdef _WIN32 + DWORD len = 0; + auto ret = GetAdaptersInfo(nullptr, &len); + if (ret != ERROR_BUFFER_OVERFLOW) return false; + std::vector buf(len); + auto pips = reinterpret_cast(&buf.front()); + ret = GetAdaptersInfo(pips, &len); + if (ret != ERROR_SUCCESS) return false; + mac_address addr; + std::copy(pips->Address, pips->Address + 6, std::begin(addr)); + device_address = addr; +#endif + + return device_address.has_value(); + } + + [[nodiscard]] long long get_time_intervals() { + auto start = std::chrono::system_clock::from_time_t(time_t(-12219292800)); + auto diff = std::chrono::system_clock::now() - start; + auto ns = std::chrono::duration_cast(diff).count(); + return ns / 100; + } + + [[nodiscard]] static unsigned short get_clock_sequence() { + static std::mt19937 clock_gen(std::random_device{}()); + static std::uniform_int_distribution clock_dis; + static std::atomic_ushort clock_sequence = clock_dis(clock_gen); + return clock_sequence++; + } + + public: + [[nodiscard]] uuid operator()() { + if (get_mac_address()) { + std::array data; + + auto tm = get_time_intervals(); + + auto clock_seq = get_clock_sequence(); + + auto ptm = reinterpret_cast(&tm); + + memcpy(&data[0], ptm + 4, 4); + memcpy(&data[4], ptm + 2, 2); + memcpy(&data[6], ptm, 2); + + memcpy(&data[8], &clock_seq, 2); + + // variant must be 0b10xxxxxx + data[8] &= 0xBF; + data[8] |= 0x80; + + // version must be 0b0001xxxx + data[6] &= 0x1F; + data[6] |= 0x10; + + memcpy(&data[10], &device_address.value()[0], 6); + + return uuids::uuid{std::cbegin(data), std::cend(data)}; + } + + return {}; + } +}; +#endif +} // namespace uuids + +namespace std { +template <> +struct hash { + using argument_type = uuids::uuid; + using result_type = std::size_t; + + [[nodiscard]] result_type operator()(argument_type const& uuid) const { +#ifdef UUID_HASH_STRING_BASED + std::hash hasher; + return static_cast(hasher(uuids::to_string(uuid))); +#else + uint64_t l = + static_cast(uuid.data[0]) << 56 | static_cast(uuid.data[1]) << 48 | + static_cast(uuid.data[2]) << 40 | static_cast(uuid.data[3]) << 32 | + static_cast(uuid.data[4]) << 24 | static_cast(uuid.data[5]) << 16 | + static_cast(uuid.data[6]) << 8 | static_cast(uuid.data[7]); + uint64_t h = + static_cast(uuid.data[8]) << 56 | static_cast(uuid.data[9]) << 48 | + static_cast(uuid.data[10]) << 40 | static_cast(uuid.data[11]) << 32 | + static_cast(uuid.data[12]) << 24 | static_cast(uuid.data[13]) << 16 | + static_cast(uuid.data[14]) << 8 | static_cast(uuid.data[15]); + + if constexpr (sizeof(result_type) > 4) { + return result_type(l ^ h); + } else { + uint64_t hash64 = l ^ h; + return result_type(uint32_t(hash64 >> 32) ^ uint32_t(hash64)); + } +#endif + } +}; +} // namespace std + +#endif /* STDUUID_H */ diff --git a/src/CompilerKit/Version.h b/src/CompilerKit/Version.h new file mode 100644 index 0000000..efd174c --- /dev/null +++ b/src/CompilerKit/Version.h @@ -0,0 +1,15 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#define kDistVersion "v0.0.7-compilerkit" +#define kDistVersionBCD 0x0002 + +#define ToString(X) Stringify(X) +#define Stringify(X) #X + +#define kDistRelease ToString(kDistReleaseBranch) diff --git a/src/CompilerKit/XCOFF.h b/src/CompilerKit/XCOFF.h new file mode 100644 index 0000000..a61949a --- /dev/null +++ b/src/CompilerKit/XCOFF.h @@ -0,0 +1,43 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + + File: XCOFF.h + Purpose: XCOFF for NeKernel. + + Revision History: + + 04/07/24: Added file (Amlal El Mahrouss) + +======================================== */ + +#ifndef _NECTI_XCOFF_H_ +#define _NECTI_XCOFF_H_ + +#include + +#define kXCOFF64Magic 0x01F7 + +#define kXCOFFRelFlg 0x0001 +#define kXCOFFExecutable 0x0002 +#define kXCOFFLnno 0x0004 +#define kXCOFFLSyms 0x0008 + +namespace CompilerKit { +struct XCoffFileHeader; + +/// @brief XCoff file header. +typedef struct XCoffFileHeader { + UInt16 fMagic; + UInt16 fTarget; + UInt16 fNumSecs; + UInt32 fTimeDat; + UIntPtr fSymPtr; + UInt32 fNumSyms; + UInt16 fOptHdr; // ?: Number of bytes in optional header +} XCoffFileHeader; + +typedef struct XCoffFileHeader* XCoffFileHeaderPtr; +} // namespace CompilerKit + +#endif // ifndef _NECTI_XCOFF_H_ diff --git a/src/CompilerKit/ck-osx-san.json b/src/CompilerKit/ck-osx-san.json new file mode 100644 index 0000000..df1c36b --- /dev/null +++ b/src/CompilerKit/ck-osx-san.json @@ -0,0 +1,30 @@ +{ + "compiler_path": "clang++", + "compiler_std": "c++20", + "headers_path": [ + "../CompilerKit", + "../", + "../CompilerKit/src/", + "../CompilerKit/src/impl", + "/opt/homebrew/Cellar/boost/1.87.0/include" + ], + "sources_path": [ + "src/*.cc", + "src/*/*.cc" + ], + "output_name": "/usr/local/lib/libCompilerKit.dylib", + "compiler_flags": [ + "-fPIC", + "-shared", + "-fstack-protector-all", + "-fno-omit-frame-pointer", + "-g", + "-fsanitize=address", + "-fsanitize=undefined" + ], + "cpp_macros": [ + "__NECTI__=202505", + "CK_USE_STRUCTS=1", + "kDistReleaseBranch=$(git rev-parse --abbrev-ref HEAD)-$(uuidgen)" + ] +} \ No newline at end of file diff --git a/src/CompilerKit/ck-osx.json b/src/CompilerKit/ck-osx.json new file mode 100644 index 0000000..cab0221 --- /dev/null +++ b/src/CompilerKit/ck-osx.json @@ -0,0 +1,25 @@ +{ + "compiler_path": "clang++", + "compiler_std": "c++20", + "headers_path": [ + "../CompilerKit", + "../", + "../CompilerKit/src/", + "../CompilerKit/src/impl", + "/opt/homebrew/Cellar/boost/1.87.0/include" + ], + "sources_path": [ + "src/*.cc", + "src/*/*.cc" + ], + "output_name": "/usr/local/lib/libCompilerKit.dylib", + "compiler_flags": [ + "-fPIC", + "-shared" + ], + "cpp_macros": [ + "__NECTI__=202505", + "CK_USE_STRUCTS=1", + "kDistReleaseBranch=$(git rev-parse --abbrev-ref HEAD)-$(uuidgen)" + ] +} \ No newline at end of file diff --git a/src/CompilerKit/ck-posix.json b/src/CompilerKit/ck-posix.json new file mode 100644 index 0000000..b748f7a --- /dev/null +++ b/src/CompilerKit/ck-posix.json @@ -0,0 +1,24 @@ +{ + "compiler_path": "clang++", + "compiler_std": "c++20", + "headers_path": [ + "../CompilerKit", + "../", + "../CompilerKit/src/", + "../CompilerKit/src/impl" + ], + "sources_path": [ + "src/*.cc", + "src/*/*.cc" + ], + "output_name": "/usr/lib/libCompilerKit.so", + "compiler_flags": [ + "-fPIC", + "-shared" + ], + "cpp_macros": [ + "__NECTI__=202505", + "CK_USE_STRUCTS=1", + "kDistReleaseBranch=$(git rev-parse --abbrev-ref HEAD)-$(uuidgen)" + ] +} diff --git a/src/CompilerKit/impl/32x0.h b/src/CompilerKit/impl/32x0.h new file mode 100644 index 0000000..1ad13ee --- /dev/null +++ b/src/CompilerKit/impl/32x0.h @@ -0,0 +1,93 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include + +// @brief Open32x0 support. +// @file impl/32x0.h + +#define CK_ASM_OPCODE(__NAME, __OPCODE, __FUNCT3, __FUNCT7) \ + {.fName = __NAME, .fOpcode = __OPCODE, .fFunct3 = __FUNCT3, .fFunct7 = __FUNCT7}, + +#define kAsmImmediate 0x01 +#define kAsmSyscall 0x02 +#define kAsmJump 0x03 +#define kAsmNoArgs 0x04 + +#define kAsmByte 0 +#define kAsmHWord 1 +#define kAsmWord 2 + +struct CpuCode32x0 { + const char fName[32]; + uint8_t fOpcode; + uint8_t fSize; + uint8_t fFunct3; + uint8_t fFunct7; +}; + +#define kAsmDWordStr ".dword" /* 64 bit */ +#define kAsmWordStr ".word" /* 32-bit */ +#define kAsmHWordStr ".half" /* 16-bit */ +#define kAsmByteStr ".byte" /* 8-bit */ + +inline std::vector kOpcodes32x0 = { + CK_ASM_OPCODE("nop", 0b0100011, 0b000, kAsmNoArgs) // nothing to do. (1C) + CK_ASM_OPCODE("jmp", 0b1110011, 0b001, kAsmJump) // jump to branch (2C) + CK_ASM_OPCODE("mov", 0b0100011, 0b101, kAsmImmediate) // move registers (3C) + CK_ASM_OPCODE("psh", 0b0111011, 0b000, kAsmImmediate) // push to sp (2C) + CK_ASM_OPCODE("pop", 0b0111011, 0b001, kAsmImmediate) // pop from sp. (1C) + CK_ASM_OPCODE("lea", 0b0111011, 0b010, + kAsmImmediate) // setup stack and call, store address to CR (1C). + CK_ASM_OPCODE("ret", 0b0111011, 0b110, + kAsmImmediate) // return from procedure (2C). + CK_ASM_OPCODE("uc", 0b0111111, 0b000, kAsmSyscall) // user call (1C) + CK_ASM_OPCODE("kc", 0b0111111, 0b001, kAsmSyscall) // kernel call (1C) + CK_ASM_OPCODE("int", 0b0111111, 0b010, kAsmSyscall) // raise interrupt (1C) +}; + +// \brief 64x0 register prefix +// example: r32, r0 +// r32 -> sp +// r0 -> hw zero + +#define kAsmRegisterPrefix "r" +#define kAsmRegisterLimit 16 +#define kAsmPcRegister 17 +#define kAsmCrRegister 18 +#define kAsmSpRegister 5 + +/* return address register */ +#define kAsmRetRegister 19 + +///////////////////////////////////////////////////////////////////////////// + +// SYSTEM CALL ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | OFF | + +// IMMEDIATE ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | REG | +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG | OFF | + +// REG TO REG ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG2 | + +//////////////////////////////// + +// LOAD/CALL INTERRUPTS + +// SET A HANDLER IN ADDRESS: TODO: find one +// DISABLE INTERRUPTS +// PROCESS INTERRUPT +// ENABLE INTERRUPTS + +//////////////////////////////// diff --git a/src/CompilerKit/impl/64x0.h b/src/CompilerKit/impl/64x0.h new file mode 100644 index 0000000..ce2ae89 --- /dev/null +++ b/src/CompilerKit/impl/64x0.h @@ -0,0 +1,100 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include + +// @brief Open64x0 support. +// @file impl/64x0.h + +#define CK_ASM_OPCODE(__NAME, __OPCODE, __FUNCT3, __FUNCT7) \ + {.fName = __NAME, .fOpcode = __OPCODE, .fFunct3 = __FUNCT3, .fFunct7 = __FUNCT7}, + +#define kAsmImmediate 0x01 +#define kAsmRegToReg 0x02 +#define kAsmSyscall 0x03 +#define kAsmJump 0x04 +#define kAsmNoArgs 0x00 + +typedef char e64k_character_t; +typedef uint8_t e64k_num_t; + +struct CpuOpcode64x0 { + const e64k_character_t fName[32]; + e64k_num_t fOpcode; + e64k_num_t fFunct3; + e64k_num_t fFunct7; +}; + +inline std::vector kOpcodes64x0 = { + CK_ASM_OPCODE("nop", 0b0000000, 0b0000000, kAsmNoArgs) // no-operation. + CK_ASM_OPCODE("np", 0b0000000, 0b0000000, kAsmNoArgs) // no-operation. + CK_ASM_OPCODE("jlr", 0b1110011, 0b0000111, + kAsmJump) // jump to linked return register + CK_ASM_OPCODE("jrl", 0b1110011, 0b0001111, + kAsmJump) // jump from return register. + CK_ASM_OPCODE("mv", 0b0100011, 0b101, kAsmRegToReg) CK_ASM_OPCODE( + "bg", 0b1100111, 0b111, kAsmRegToReg) CK_ASM_OPCODE("bl", 0b1100111, 0b011, kAsmRegToReg) + CK_ASM_OPCODE("beq", 0b1100111, 0b000, kAsmRegToReg) + CK_ASM_OPCODE("bne", 0b1100111, 0b001, kAsmRegToReg) + CK_ASM_OPCODE("bge", 0b1100111, 0b101, kAsmRegToReg) + CK_ASM_OPCODE("ble", 0b1100111, 0b100, kAsmRegToReg) + CK_ASM_OPCODE("stw", 0b0001111, 0b100, kAsmImmediate) + CK_ASM_OPCODE("ldw", 0b0001111, 0b100, kAsmImmediate) + CK_ASM_OPCODE("lda", 0b0001111, 0b101, kAsmImmediate) + CK_ASM_OPCODE("sta", 0b0001111, 0b001, kAsmImmediate) + // add/sub without carry flag + CK_ASM_OPCODE("add", 0b0101011, 0b100, kAsmImmediate) + CK_ASM_OPCODE("sub", 0b0101011, 0b101, kAsmImmediate) + // add/sub with carry flag + CK_ASM_OPCODE("addc", 0b0101011, 0b110, kAsmImmediate) CK_ASM_OPCODE( + "subc", 0b0101011, 0b111, kAsmImmediate) CK_ASM_OPCODE("sc", 0b1110011, 0b00, kAsmSyscall)}; + +// \brief 64x0 register prefix +// example: r32, r0 +// r32 -> sp +// r0 -> hw zero + +#define kAsmFloatZeroRegister 0 +#define kAsmZeroRegister 0 + +#define kAsmRegisterPrefix "r" +#define kAsmRegisterLimit 30 +#define kAsmPcRegister 17 +#define kAsmCrRegister 18 +#define kAsmSpRegister 5 + +/* return address register */ +#define kAsmRetRegister 19 + +///////////////////////////////////////////////////////////////////////////// + +// SYSTEM CALL/JUMP ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | OFF | + +// IMMEDIATE ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | REG | +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG | OFF | + +// REG TO REG ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG2 | + +//////////////////////////////// + +// LOAD/CALL INTERRUPTS + +// SET A HANDLER IN ADDRESS: +// DISABLE INTERRUPTS +// PROCESS INTERRUPT +// ENABLE INTERRUPTS + +//////////////////////////////// diff --git a/src/CompilerKit/impl/Aarch64.h b/src/CompilerKit/impl/Aarch64.h new file mode 100644 index 0000000..c1e9c77 --- /dev/null +++ b/src/CompilerKit/impl/Aarch64.h @@ -0,0 +1,41 @@ +/* ======================================== + +Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include + +/// @brief ARM64 encoding support. +/// @file impl/Aarch64.h + +struct CpuOpcodeArm64; + +/// @brief ARM64 opcode header. +struct PACKED CpuOpcodeArm64_Data final { + uint32_t fOpcode : 10; // Bits 31–22: Opcode for operation + uint32_t fRm : 5; // Bits 21–16: Source register Rm + uint32_t fShamt : 6; // Bits 15–10: Shift amount + uint32_t fRn : 5; // Bits 9–5: Source register Rn + uint32_t fRd : 5; // Bits 4–0: Destination register Rd +}; + +typedef struct { + uint32_t opcode : 6; // Bits 31–26: Branch opcode + int32_t offset : 26; // Bits 25–0: Signed offset (branch target) +} PACKED CpuOpcodeArm64_Branch; + +typedef struct { + uint32_t size : 2; // Bits 31–30: Size of the data + uint32_t opcode : 7; // Bits 29–23: Opcode for load/store + uint32_t offset : 12; // Bits 22–10: Offset + uint32_t rn : 5; // Bits 9–5: Base address register Rn + uint32_t rt : 5; // Bits 4–0: Target/source register Rt +} PACKED CpuOpcodeArm64_LoadStore; + +#define kAsmRegisterLimit (30) +#define kAsmRegisterPrefix "x" +#define kOpcodeARM64Count (1000) diff --git a/src/CompilerKit/impl/PowerPC.h b/src/CompilerKit/impl/PowerPC.h new file mode 100644 index 0000000..7c7f67c --- /dev/null +++ b/src/CompilerKit/impl/PowerPC.h @@ -0,0 +1,1557 @@ +/* ======================================== + + Some modifications are copyrighted under: + Amlal El Mahrouss + + Original author: + Apple Inc + +======================================== */ + +#pragma once + +#include + +/// @note Based of: +/// https://opensource.apple.com/source/cctools/cctools-750/as/ppc-opcode.h.auto.html + +#define kOpcodePPCCount (1073U) + +/* + * These defines are use in the cpus field of the instructions. If the field + * is zero it can execute on all cpus. The defines are or'ed together. This + * information is used to set the cpusubtype in the resulting object file. + */ +#define CPU601 0x1 +#define IMPL64 0x2 +#define OPTIONAL 0x4 +#define VMX 0x8 +#define CPU970 0x10 /* added to OPTIONAL insts that the 970 has */ +#define CPUMAHROUSS 0x12 /* optional mahrouss insts. */ + +enum OpcodeType { + NONE, /* no operand */ + JBSR, /* jbsr pseudo op */ + PCREL, /* PC relative (branch offset) */ + BADDR, /* Branch address (sign extended absolute address) */ + D, /* 16 bit displacement */ + DS, /* 14 bit displacement (double word) */ + SI, /* signed 16 bit immediate */ + UI, /* unsigned 16 bit immediate */ + HI, /* high 16 bit immediate (with truncation) */ + GREG, /* general register */ + G0REG, /* general register r1-r31 or 0 */ + FREG, /* float register */ + VREG, /* vector register */ + SGREG, /* segment register */ + SPREG, /* special register (or 10 bit number, 5 bit halves reversed) */ + BCND, /* branch condition opcode */ + CRF, /* condition register field */ + CRFONLY, /* condition register field only no expression allowed */ + sh, /* 6 bit number (0 - 63) (sh field, split and reversed) */ + mb, /* 6 bit number (0 - 63) (mb field, mb5 || mb0:4 reversed) */ + NUM, /* number */ + SNUM, /* signed number */ + NUM0, /* number (where 1< + +// @brief AMD64 support. +// @file impl/X64.h + +#define CK_ASM_OPCODE(__NAME, __OPCODE) {.fName = __NAME, .fOpcode = __OPCODE}, + +typedef char i64_character_t; +typedef uint8_t i64_byte_t; +typedef uint16_t i64_hword_t; +typedef uint32_t i64_word_t; + +#define kAsmRegisterPrefix "r" + +struct CpuOpcodeAMD64 { + std::string fName; + i64_byte_t fPrefixBytes[4]; + i64_hword_t fOpcode; + i64_hword_t fModReg; + i64_word_t fDisplacment; + i64_word_t fImmediate; +}; + +/// these two are edge cases +#define kAsmIntOpcode 0xCC +#define kasmIntOpcodeAlt 0xCD + +#define kAsmJumpOpcode 0x0F80 +#define kJumpLimit 30 +#define kJumpLimitStandard 0xE3 +#define kJumpLimitStandardLimit 0xEB + +inline std::vector kOpcodesAMD64 = { + CK_ASM_OPCODE("int", 0xCD) CK_ASM_OPCODE("into", 0xCE) CK_ASM_OPCODE("intd", 0xF1) + CK_ASM_OPCODE("int3", 0xC3) CK_ASM_OPCODE("iret", 0xCF) CK_ASM_OPCODE("retf", 0xCB) + CK_ASM_OPCODE("retn", 0xC3) CK_ASM_OPCODE("ret", 0xC3) CK_ASM_OPCODE("sti", 0xfb) + CK_ASM_OPCODE("cli", 0xfa) CK_ASM_OPCODE("hlt", 0xf4) CK_ASM_OPCODE("nop", 0x90) + CK_ASM_OPCODE("mov", 0x48) CK_ASM_OPCODE("call", 0xFF) + CK_ASM_OPCODE("syscall", 0x0F) CK_ASM_OPCODE("xor", 0x48)}; + +#define kAsmRegisterLimit 16 diff --git a/src/CompilerKit/src/AssemblyFactory.cc b/src/CompilerKit/src/AssemblyFactory.cc new file mode 100644 index 0000000..5c606e9 --- /dev/null +++ b/src/CompilerKit/src/AssemblyFactory.cc @@ -0,0 +1,52 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#include +#include + +/** + * @file AssemblyFactory.cc + * @author Amlal El Mahrouss (amlal@nekernel.org) + * @brief Assembly API of NeCTI + * @version 0.0.2 + * + * @copyright Copyright (c) 2024-2025 Amlal El Mahrouss + * + */ + +namespace CompilerKit { +///! @brief Compile for specific format (ELF, PEF, ZBIN) +Int32 AssemblyFactory::Compile(STLString sourceFile, const Int32& arch) noexcept { + if (sourceFile.length() < 1) return NECTI_UNIMPLEMENTED; + + if (!fMounted) return NECTI_UNIMPLEMENTED; + if (arch != fMounted->Arch()) return NECTI_INVALID_ARCH; + + try { + return this->fMounted->CompileToFormat(sourceFile, arch); + } catch (std::exception& e) { + return NECTI_EXEC_ERROR; + } +} + +///! @brief mount assembly backend. +void AssemblyFactory::Mount(AssemblyInterface* mountPtr) noexcept { + if (mountPtr) { + fMounted = mountPtr; + } +} + +///! @brief Unmount assembler. +AssemblyInterface* AssemblyFactory::Unmount() noexcept { + auto mount_prev = fMounted; + + if (fMounted) { + fMounted = nullptr; + } + + return mount_prev; +} +} // namespace CompilerKit diff --git a/src/CompilerKit/src/Backend/Assembler32x0.cc b/src/CompilerKit/src/Backend/Assembler32x0.cc new file mode 100644 index 0000000..6ffaa6e --- /dev/null +++ b/src/CompilerKit/src/Backend/Assembler32x0.cc @@ -0,0 +1,39 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +/// bugs: 0 + +///////////////////////////////////////////////////////////////////////////////////////// + +// @file 32asm.cc +// @author Amlal El Mahrouss +// @brief 32x0 Assembler. + +// REMINDER: when dealing with an undefined symbol use (string +// size):LinkerFindSymbol:(string) so that ld will look for it. + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __ASM_NEED_32x0__ +#define __ASM_NEED_32x0__ 1 +#endif + +#include +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief 32x0 Assembler entrypoint, the program/module starts here. + +///////////////////////////////////////////////////////////////////////////////////////// + +NECTI_MODULE(NEAssemblerMain32000) { + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + return EXIT_SUCCESS; +} diff --git a/src/CompilerKit/src/Backend/Assembler64x0.cc b/src/CompilerKit/src/Backend/Assembler64x0.cc new file mode 100644 index 0000000..f9052d2 --- /dev/null +++ b/src/CompilerKit/src/Backend/Assembler64x0.cc @@ -0,0 +1,874 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +/// bugs: 0 + +///////////////////////////////////////////////////////////////////////////////////////// + +// @file Assembler64x0.cc +// @author Amlal El Mahrouss +// @brief 64x000 Assembler. + +// REMINDER: when dealing with an undefined symbol use (string +// size):LinkerFindSymbol:(string) so that ld will look for it. + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __ASM_NEED_64x0__ +#define __ASM_NEED_64x0__ 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///////////////////// + +// ANSI ESCAPE CODES + +///////////////////// + +static char kOutputArch = CompilerKit::kPefArch64000; + +constexpr auto k64x0IPAlignment = 0x1U; + +static std::size_t kCounter = 1UL; + +static std::uintptr_t kOrigin = kPefBaseOrigin; +static std::vector> kOriginLabel; + +static std::vector kBytes; + +static CompilerKit::AERecordHeader kCurrentRecord{ + .fName = "", .fKind = CompilerKit::kPefCode, .fSize = 0, .fOffset = 0}; + +static std::vector kRecords; +static std::vector kUndefinedSymbols; + +static const std::string kUndefinedSymbol = ":UndefinedSymbol:"; +static const std::string kRelocSymbol = ":RuntimeSymbol:"; + +// \brief forward decl. +static bool asm_read_attributes(std::string line); + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief 64x0 assembler entrypoint, the program/module starts here. + +///////////////////////////////////////////////////////////////////////////////////////// + +NECTI_MODULE(AssemblerMain64x0) { + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + + for (size_t i = 1; i < argc; ++i) { + if (argv[i][0] == '-') { + if (strcmp(argv[i], "-64x0-ver") == 0 || strcmp(argv[i], "-64x0-v") == 0) { + kStdOut + << "Assembler64x0: 64x0 Assembler.\nAssembler64x0: v1.10\nAssembler64x0: Copyright (c) " + "Amlal El Mahrouss\n"; + return 0; + } else if (strcmp(argv[i], "-64x0-h") == 0) { + kStdOut << "Assembler64x0: 64x0 Assembler.\nAssembler64x0: Copyright (c) 2024 Mahrouss " + "Logic.\n"; + kStdOut << "--version: Print program version.\n"; + kStdOut << "--verbose: Print verbose output.\n"; + kStdOut << "--binary: Output as flat binary.\n"; + kStdOut << "--64xxx: Compile for a subset of the X64000.\n"; + + return 0; + } else if (strcmp(argv[i], "-64x0-binary") == 0) { + kOutputAsBinary = true; + continue; + } else if (strcmp(argv[i], "-64x0-verbose") == 0) { + kVerbose = true; + continue; + } + + kStdOut << "Assembler64x0: ignore " << argv[i] << "\n"; + continue; + } + + if (!std::filesystem::exists(argv[i])) { + kStdOut << "Assembler64x0: can't open: " << argv[i] << std::endl; + goto asm_fail_exit; + } + + std::string object_output(argv[i]); + + for (auto& ext : kAsmFileExts) { + if (object_output.find(ext) != std::string::npos) { + object_output.erase(object_output.find(ext), std::strlen(ext)); + } + } + + object_output += kOutputAsBinary ? kBinaryFileExt : kObjectFileExt; + + std::ifstream file_ptr(argv[i]); + std::ofstream file_ptr_out(object_output, std::ofstream::binary); + + if (file_ptr_out.bad()) { + if (kVerbose) { + kStdOut << "Assembler64x0: error: " << strerror(errno) << "\n"; + } + } + + std::string line; + + CompilerKit::AEHeader hdr{0}; + + memset(hdr.fPad, kAENullType, kAEPad); + + hdr.fMagic[0] = kAEMag0; + hdr.fMagic[1] = kAEMag1; + hdr.fSize = sizeof(CompilerKit::AEHeader); + hdr.fArch = kOutputArch; + + ///////////////////////////////////////////////////////////////////////////////////////// + + // COMPILATION LOOP + + ///////////////////////////////////////////////////////////////////////////////////////// + + CompilerKit::Encoder64x0 asm64; + + while (std::getline(file_ptr, line)) { + if (auto ln = asm64.CheckLine(line, argv[i]); !ln.empty()) { + Detail::print_error(ln, argv[i]); + continue; + } + + try { + asm_read_attributes(line); + asm64.WriteLine(line, argv[i]); + } catch (const std::exception& e) { + if (kVerbose) { + std::string what = e.what(); + Detail::print_warning("exit because of: " + what, "CompilerKit"); + } + + std::filesystem::remove(object_output); + goto asm_fail_exit; + } + } + + if (!kOutputAsBinary) { + if (kVerbose) { + kStdOut << "Assembler64x0: Writing object file...\n"; + } + + // this is the final step, write everything to the file. + + auto pos = file_ptr_out.tellp(); + + hdr.fCount = kRecords.size() + kUndefinedSymbols.size(); + + file_ptr_out << hdr; + + if (kRecords.empty()) { + kStdErr << "Assembler64x0: At least one record is needed to write an object " + "file.\nAssembler64x0: Make one using `public_segment .code64 foo_bar`.\n"; + + std::filesystem::remove(object_output); + return 1; + } + + kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + std::size_t record_count = 0UL; + + for (auto& rec : kRecords) { + if (kVerbose) kStdOut << "Assembler64x0: Wrote record " << rec.fName << " to file...\n"; + + rec.fFlags |= CompilerKit::kKindRelocationAtRuntime; + rec.fOffset = record_count; + ++record_count; + + file_ptr_out << rec; + } + + // increment once again, so that we won't lie about the kUndefinedSymbols. + ++record_count; + + for (auto& sym : kUndefinedSymbols) { + CompilerKit::AERecordHeader _record_hdr{0}; + + if (kVerbose) kStdOut << "Assembler64x0: Wrote symbol " << sym << " to file...\n"; + + _record_hdr.fKind = kAENullType; + _record_hdr.fSize = sym.size(); + _record_hdr.fOffset = record_count; + + ++record_count; + + memset(_record_hdr.fPad, kAENullType, kAEPad); + memcpy(_record_hdr.fName, sym.c_str(), sym.size()); + + file_ptr_out << _record_hdr; + + ++kCounter; + } + + auto pos_end = file_ptr_out.tellp(); + + file_ptr_out.seekp(pos); + + hdr.fStartCode = pos_end; + hdr.fCodeSize = kBytes.size(); + + file_ptr_out << hdr; + + file_ptr_out.seekp(pos_end); + } else { + if (kVerbose) { + kStdOut << "Assembler64x0: Write raw binary...\n"; + } + } + + // byte from byte, we write this. + for (auto& byte : kBytes) { + file_ptr_out.write(reinterpret_cast(&byte), sizeof(byte)); + } + + if (kVerbose) kStdOut << "Assembler64x0: Wrote file with program in it.\n"; + + file_ptr_out.flush(); + file_ptr_out.close(); + + if (kVerbose) kStdOut << "Assembler64x0: Exit succeeded.\n"; + + return 0; + } + +asm_fail_exit: + + if (kVerbose) kStdOut << "Assembler64x0: Exit failed.\n"; + + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for attributes +// returns true if any was found. + +///////////////////////////////////////////////////////////////////////////////////////// + +static bool asm_read_attributes(std::string line) { + // extern_segment is the opposite of public_segment, it signals to the ld + // that we need this symbol. + if (CompilerKit::find_word(line, "extern_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid extern_segment directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_extern_segment_bin"); + } + + auto name = line.substr(line.find("extern_segment") + strlen("extern_segment")); + + /// sanity check to avoid stupid linker errors. + if (name.size() == 0) { + Detail::print_error("Invalid extern_segment", "power-as"); + throw std::runtime_error("invalid_extern_segment"); + } + + std::string result = std::to_string(name.size()); + result += kUndefinedSymbol; + + // mangle this + for (char& j : name) { + if (j == ' ' || j == ',') j = '$'; + } + + result += name; + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that ld can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, result.c_str(), result.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + // public_segment is a special keyword used by Assembler64x0 to tell the AE output stage to + // mark this section as a header. it currently supports .code64, .data64., + // .zero64 + else if (CompilerKit::find_word(line, "public_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid public_segment directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_public_segment_bin"); + } + + auto name = line.substr(line.find("public_segment") + strlen("public_segment")); + + std::string name_copy = name; + + for (char& j : name) { + if (j == ' ') j = '$'; + } + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + + name_copy.erase(name_copy.find(".code64"), strlen(".code64")); + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + + name_copy.erase(name_copy.find(".data64"), strlen(".data64")); + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + + name_copy.erase(name_copy.find(".zero64"), strlen(".zero64")); + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that ld can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + while (name_copy.find(" ") != std::string::npos) name_copy.erase(name_copy.find(" "), 1); + + kOriginLabel.push_back(std::make_pair(name_copy, kOrigin)); + ++kOrigin; + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, name.c_str(), name.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + + return false; +} + +// \brief algorithms and helpers. + +namespace Detail::algorithm { +// \brief authorize a brief set of characters. +static inline bool is_not_alnum_space(char c) { + return !(isalpha(c) || isdigit(c) || (c == ' ') || (c == '\t') || (c == ',') || (c == '(') || + (c == ')') || (c == '"') || (c == '\'') || (c == '[') || (c == ']') || (c == '+') || + (c == '_') || (c == ':') || (c == '@') || (c == '.')); +} + +bool is_valid_64x0(std::string str) { + return std::find_if(str.begin(), str.end(), is_not_alnum_space) == str.end(); +} +} // namespace Detail::algorithm + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for line (syntax check) + +///////////////////////////////////////////////////////////////////////////////////////// + +std::string CompilerKit::Encoder64x0::CheckLine(std::string line, std::string file) { + std::string err_str; + + if (line.empty() || CompilerKit::find_word(line, "extern_segment") || + CompilerKit::find_word(line, "public_segment") || line.find('#') != std::string::npos || + CompilerKit::find_word(line, ";")) { + if (line.find('#') != std::string::npos) { + line.erase(line.find('#')); + } else if (line.find(';') != std::string::npos) { + line.erase(line.find(';')); + } else { + // now check the line for validity + if (!Detail::algorithm::is_valid_64x0(line)) { + err_str = "Line contains non alphanumeric characters.\nhere -> "; + err_str += line; + } + } + + return err_str; + } + + if (!Detail::algorithm::is_valid_64x0(line)) { + err_str = "Line contains non alphanumeric characters.\nhere -> "; + err_str += line; + + return err_str; + } + + // check for a valid instruction format. + + if (line.find(',') != std::string::npos) { + if (line.find(',') + 1 == line.size()) { + err_str += "\nInstruction lacks right register, here -> "; + err_str += line.substr(line.find(',')); + + return err_str; + } else { + bool nothing_on_right = true; + + if (line.find(',') + 1 > line.size()) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + + auto substr = line.substr(line.find(',') + 1); + + for (auto& ch : substr) { + if (ch != ' ' && ch != '\t') { + nothing_on_right = false; + } + } + + // this means we found nothing after that ',' . + if (nothing_on_right) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + } + } + + // these do take an argument. + std::vector operands_inst = {"stw", "ldw", "lda", "sta"}; + + // these don't. + std::vector filter_inst = {"jlr", "jrl", "int"}; + + for (auto& opcode64x0 : kOpcodes64x0) { + if (line.find(opcode64x0.fName) != std::string::npos) { + if (opcode64x0.fFunct7 == kAsmNoArgs) return err_str; + + for (auto& op : operands_inst) { + // if only the instruction was found. + if (line == op) { + err_str += "\nMalformed "; + err_str += op; + err_str += " instruction, here -> "; + err_str += line; + } + } + + // if it is like that -> addr1, 0x0 + if (auto it = std::find(filter_inst.begin(), filter_inst.end(), opcode64x0.fName); + it == filter_inst.cend()) { + if (CompilerKit::find_word(line, opcode64x0.fName)) { + if (!isspace(line[line.find(opcode64x0.fName) + strlen(opcode64x0.fName)])) { + err_str += "\nMissing space between "; + err_str += opcode64x0.fName; + err_str += " and operands.\nhere -> "; + err_str += line; + } + } + } + + return err_str; + } + } + + err_str += "Unrecognized instruction: " + line; + + return err_str; +} + +bool CompilerKit::Encoder64x0::WriteNumber(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_hex_number"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16)); + + for (char& i : num.number) { + kBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "Assembler64x0: found a base 16 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; + } + case 'b': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "Assembler64x0: found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + kBytes.push_back(i); + } + + return true; + } + case 'o': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "Assembler64x0: found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + kBytes.push_back(i); + } + + return true; + } + default: { + break; + } + } + + /* check for errno and stuff like that */ + if (auto res = strtol(jump_label.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + return false; + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos).c_str(), nullptr, 10)); + + for (char& i : num.number) { + kBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "Assembler64x0: found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Read and write an instruction to the output array. + +///////////////////////////////////////////////////////////////////////////////////////// + +bool CompilerKit::Encoder64x0::WriteLine(std::string line, std::string file) { + if (CompilerKit::find_word(line, "public_segment ")) return true; + + for (auto& opcode64x0 : kOpcodes64x0) { + // strict check here + if (CompilerKit::find_word(line, opcode64x0.fName) && Detail::algorithm::is_valid_64x0(line)) { + std::string name(opcode64x0.fName); + std::string jump_label, cpy_jump_label; + + kBytes.emplace_back(opcode64x0.fOpcode); + kBytes.emplace_back(opcode64x0.fFunct3); + kBytes.emplace_back(opcode64x0.fFunct7); + + // check funct7 type. + switch (opcode64x0.fFunct7) { + // reg to reg means register to register transfer operation. + case kAsmRegToReg: + case kAsmImmediate: { + // \brief how many registers we found. + std::size_t found_some = 0UL; + + for (size_t line_index = 0UL; line_index < line.size(); line_index++) { + if (line[line_index] == kAsmRegisterPrefix[0] && isdigit(line[line_index + 1])) { + std::string register_syntax = kAsmRegisterPrefix; + register_syntax += line[line_index + 1]; + + if (isdigit(line[line_index + 2])) register_syntax += line[line_index + 2]; + + std::string reg_str; + reg_str += line[line_index + 1]; + + if (isdigit(line[line_index + 2])) reg_str += line[line_index + 2]; + + // it ranges from r0 to r19 + // something like r190 doesn't exist in the instruction set. + if (kOutputArch == CompilerKit::kPefArch64000) { + if (isdigit(line[line_index + 3]) && isdigit(line[line_index + 2])) { + reg_str += line[line_index + 3]; + Detail::print_error("invalid register index, r" + reg_str + + "\nnote: The 64x0 accepts registers from r0 to r20.", + file); + throw std::runtime_error("invalid_register_index"); + } + } + + // finally cast to a size_t + std::size_t reg_index = strtol(reg_str.c_str(), nullptr, 10); + + if (reg_index > kAsmRegisterLimit) { + Detail::print_error("invalid register index, r" + reg_str, file); + throw std::runtime_error("invalid_register_index"); + } + + kBytes.emplace_back(reg_index); + ++found_some; + + if (kVerbose) { + kStdOut << "Assembler64x0: Register found: " << register_syntax << "\n"; + kStdOut << "Assembler64x0: Register amount in instruction: " << found_some << "\n"; + } + } + } + + // we're not in immediate addressing, reg to reg. + if (opcode64x0.fFunct7 != kAsmImmediate) { + // remember! register to register! + if (found_some == 1) { + Detail::print_error( + "Too few registers.\ntip: each Assembler64x0 register " + "starts with 'r'.\nline: " + + line, + file); + throw std::runtime_error("not_a_register"); + } + } + + if (found_some < 1 && name != "ldw" && name != "lda" && name != "stw") { + Detail::print_error("invalid combination of opcode and registers.\nline: " + line, + file); + throw std::runtime_error("invalid_comb_op_reg"); + } else if (found_some == 1 && name == "add") { + Detail::print_error("invalid combination of opcode and registers.\nline: " + line, + file); + throw std::runtime_error("invalid_comb_op_reg"); + } else if (found_some == 1 && name == "sub") { + Detail::print_error("invalid combination of opcode and registers.\nline: " + line, + file); + throw std::runtime_error("invalid_comb_op_reg"); + } + + if (found_some > 0 && name == "pop") { + Detail::print_error( + "invalid combination for opcode 'pop'.\ntip: it expects " + "nothing.\nline: " + + line, + file); + throw std::runtime_error("invalid_comb_op_pop"); + } + } + default: + break; + } + + // try to fetch a number from the name + if (name == "stw" || name == "ldw" || name == "lda" || name == "sta") { + auto where_string = name; + + // if we load something, we'd need it's symbol/literal + if (name == "stw" || name == "sta" || name == "ldw" || name == "lda" || name == "sta") + where_string = ","; + + jump_label = line; + + auto found_sym = false; + + while (jump_label.find(where_string) != std::string::npos) { + jump_label = jump_label.substr(jump_label.find(where_string) + where_string.size()); + + while (jump_label.find(" ") != std::string::npos) { + jump_label.erase(jump_label.find(" "), 1); + } + + if (jump_label[0] != kAsmRegisterPrefix[0] && !isdigit(jump_label[1])) { + if (found_sym) { + Detail::print_error( + "invalid combination of opcode and operands.\nhere -> " + jump_label, file); + throw std::runtime_error("invalid_comb_op_ops"); + } else { + // death trap installed. + found_sym = true; + } + } + } + + cpy_jump_label = jump_label; + + // replace any spaces with $ + if (jump_label[0] == ' ') { + while (jump_label.find(' ') != std::string::npos) { + if (isalnum(jump_label[0]) || isdigit(jump_label[0])) break; + + jump_label.erase(jump_label.find(' '), 1); + } + } + + if (!this->WriteNumber(0, jump_label)) { + // sta expects this: sta 0x000000, r0 + if (name == "sta") { + Detail::print_error("invalid combination of opcode and operands.\nHere ->" + line, + file); + throw std::runtime_error("invalid_comb_op_ops"); + } + } else { + if (name == "sta" && cpy_jump_label.find("extern_segment ") != std::string::npos) { + Detail::print_error("invalid usage extern_segment on 'sta', here: " + line, file); + throw std::runtime_error("invalid_sta_usage"); + } + } + + goto asm_write_label; + } + + // This is the case where we jump to a label, it is also used as a goto. + if (name == "lda" || name == "sta") { + asm_write_label: + if (cpy_jump_label.find('\n') != std::string::npos) + cpy_jump_label.erase(cpy_jump_label.find('\n'), 1); + + if (cpy_jump_label.find("extern_segment") != std::string::npos) { + cpy_jump_label.erase(cpy_jump_label.find("extern_segment"), strlen("extern_segment")); + + if (name == "sta") { + Detail::print_error("extern_segment is not allowed on a sta operation.", file); + throw std::runtime_error("extern_segment_sta_op"); + } else { + goto asm_end_label_cpy; + } + } + + if (name == "lda" || name == "sta") { + for (auto& label : kOriginLabel) { + if (cpy_jump_label == label.first) { + if (kVerbose) { + kStdOut << "Assembler64x0: Replace label " << cpy_jump_label + << " to address: " << label.second << std::endl; + } + + CompilerKit::NumberCast64 num(label.second); + + for (auto& num : num.number) { + kBytes.push_back(num); + } + + goto asm_end_label_cpy; + } + } + + if (cpy_jump_label[0] == '0') { + switch (cpy_jump_label[1]) { + case 'x': + case 'o': + case 'b': + if (this->WriteNumber(0, cpy_jump_label)) goto asm_end_label_cpy; + + break; + default: + break; + } + + if (isdigit(cpy_jump_label[0])) { + if (this->WriteNumber(0, cpy_jump_label)) goto asm_end_label_cpy; + + break; + } + } + } + + if (cpy_jump_label.size() < 1) { + Detail::print_error("label is empty, can't jump on it.", file); + throw std::runtime_error("label_empty"); + } + + /// don't go any further if: + /// load word (ldw) or store word. (stw) + + if (name == "ldw" || name == "stw") break; + + auto mld_reloc_str = std::to_string(cpy_jump_label.size()); + mld_reloc_str += kUndefinedSymbol; + mld_reloc_str += cpy_jump_label; + + bool ignore_back_slash = false; + + for (auto& reloc_chr : mld_reloc_str) { + if (reloc_chr == '\\') { + ignore_back_slash = true; + continue; + } + + if (ignore_back_slash) { + ignore_back_slash = false; + continue; + } + + kBytes.push_back(reloc_chr); + } + + kBytes.push_back('\0'); + goto asm_end_label_cpy; + } + + asm_end_label_cpy: + kOrigin += k64x0IPAlignment; + + break; + } + } + + return true; +} + +// Last rev 13-1-24 diff --git a/src/CompilerKit/src/Backend/AssemblerAMD64.cc b/src/CompilerKit/src/Backend/AssemblerAMD64.cc new file mode 100644 index 0000000..8c7e21c --- /dev/null +++ b/src/CompilerKit/src/Backend/AssemblerAMD64.cc @@ -0,0 +1,1204 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @file AssemblerAMD64.cc +/// @author Amlal El Mahrouss +/// @brief AMD64 Assembler. +/// REMINDER: when dealing with an undefined symbol use (string +/// size):LinkerFindSymbol:(string) so that ld will look for it. + +///////////////////////////////////////////////////////////////////////////////////////// + +/// bugs: 0 + +/// feature request: 1 +/// Encode registers in mov, add, xor... + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __ASM_NEED_AMD64__ +#define __ASM_NEED_AMD64__ 1 +#endif + +#define kAssemblerPragmaSymStr "%%" +#define kAssemblerPragmaSym '%' + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///////////////////// + +// ANSI ESCAPE CODES + +///////////////////// + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" +#define kYellow "\e[0;33m" + +static char kOutputArch = CompilerKit::kPefArchAMD64; + +static constexpr auto kIPAlignement = 0x1U; +static auto kCounter = 0x1UL; + +static std::uintptr_t kOrigin = kPefBaseOrigin; +static std::vector> kOriginLabel; + +/// @brief keep it simple by default. +static std::int32_t kRegisterBitWidth = 16U; + +static std::vector kAppBytes; + +static CompilerKit::AERecordHeader kCurrentRecord{ + .fName = "", .fKind = CompilerKit::kPefCode, .fSize = 0, .fOffset = 0}; + +static std::vector kRecords; +static std::vector kDefinedSymbols; +static std::vector kUndefinedSymbols; + +static const std::string kUndefinedSymbol = ":UndefinedSymbol:"; + +// \brief forward decl. +static bool asm_read_attributes(std::string line); + +#include + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief AMD64 assembler entrypoint, the program/module starts here. + +///////////////////////////////////////////////////////////////////////////////////////// + +NECTI_MODULE(AssemblerMainAMD64) { + //////////////// CPU OPCODES BEGIN //////////////// + + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + + std::string opcodes_jump[kJumpLimit] = {"ja", "jae", "jb", "jbe", "jc", "je", "jg", "jge", + "jl", "jle", "jna", "jnae", "jnb", "jnbe", "jnc", "jne", + "jng", "jnge", "jnl", "jnle", "jno", "jnp", "jns", "jnz", + "jo", "jp", "jpe", "jpo", "js", "jz"}; + + for (i64_hword_t i = 0; i < kJumpLimit; i++) { + CpuOpcodeAMD64 code{.fName = opcodes_jump[i], + .fOpcode = static_cast(kAsmJumpOpcode + i)}; + kOpcodesAMD64.push_back(code); + } + + CpuOpcodeAMD64 code{.fName = "jcxz", .fOpcode = 0xE3}; + kOpcodesAMD64.push_back(code); + + for (i64_hword_t i = kJumpLimitStandard; i < kJumpLimitStandardLimit; i++) { + CpuOpcodeAMD64 code{.fName = "jmp", .fOpcode = i}; + kOpcodesAMD64.push_back(code); + } + + CpuOpcodeAMD64 lahf{.fName = "lahf", .fOpcode = 0x9F}; + kOpcodesAMD64.push_back(lahf); + + CpuOpcodeAMD64 lds{.fName = "lds", .fOpcode = 0xC5}; + kOpcodesAMD64.push_back(lds); + + CpuOpcodeAMD64 lea{.fName = "lea", .fOpcode = 0x8D}; + kOpcodesAMD64.push_back(lea); + + CpuOpcodeAMD64 nop{.fName = "nop", .fOpcode = 0x90}; + kOpcodesAMD64.push_back(nop); + + //////////////// CPU OPCODES END //////////////// + + for (size_t i = 1; i < argc; ++i) { + if (argv[i][0] == '-') { + if (strcmp(argv[i], "--amd64:ver") == 0 || strcmp(argv[i], "--amd64:v") == 0) { + kStdOut << "AssemblerAMD64: AMD64 Assembler Driver.\nAssemblerAMD64: " + "v1.10\nAssemblerAMD64: Copyright " + "(c) Amlal El Mahrouss\n"; + return 0; + } else if (strcmp(argv[i], "--amd64:h") == 0) { + kStdOut << "AssemblerAMD64: AMD64 Assembler Driver.\nAssemblerAMD64: Copyright (c) 2024 " + "Amlal El Mahrouss\n"; + kStdOut << "--version: Print program version.\n"; + kStdOut << "--verbose: Print verbose output.\n"; + kStdOut << "--binary: Output as flat binary.\n"; + + return 0; + } else if (strcmp(argv[i], "--amd64:binary") == 0) { + kOutputAsBinary = true; + continue; + } else if (strcmp(argv[i], "--amd64:verbose") == 0) { + kVerbose = true; + continue; + } + + kStdOut << "AssemblerAMD64: ignore " << argv[i] << "\n"; + continue; + } + + if (!std::filesystem::exists(argv[i])) { + kStdOut << "AssemblerAMD64: can't open: " << argv[i] << std::endl; + goto asm_fail_exit; + } + + std::string object_output(argv[i]); + std::string asm_input(argv[i]); + + for (auto& ext : kAsmFileExts) { + if (object_output.ends_with(ext)) { + object_output.erase(object_output.find(ext), std::strlen(ext)); + break; + } + } + + object_output += kOutputAsBinary ? kBinaryFileExt : kObjectFileExt; + + std::ifstream file_ptr(argv[i]); + std::ofstream file_ptr_out(object_output, std::ofstream::binary); + + kStdOut << "AssemblerAMD64: Assembling: " << argv[i] << "\n"; + + if (file_ptr_out.bad()) { + if (kVerbose) { + kStdOut << "AssemblerAMD64: error: " << strerror(errno) << "\n"; + } + + return 1; + } + + std::string line; + + CompilerKit::AEHeader hdr{0}; + + memset(hdr.fPad, kAENullType, kAEPad); + + hdr.fMagic[0] = kAEMag0; + hdr.fMagic[1] = kAEMag1; + hdr.fSize = sizeof(CompilerKit::AEHeader); + hdr.fArch = kOutputArch; + + ///////////////////////////////////////////////////////////////////////////////////////// + + // COMPILATION LOOP + + ///////////////////////////////////////////////////////////////////////////////////////// + + CompilerKit::EncoderAMD64 asm64; + + if (kVerbose) { + kStdOut << "Compiling: " + asm_input << "\n"; + kStdOut << "From: " + line << "\n"; + } + + while (std::getline(file_ptr, line)) { + if (auto ln = asm64.CheckLine(line, argv[i]); !ln.empty()) { + Detail::print_error(ln, argv[i]); + continue; + } + + try { + asm_read_attributes(line); + asm64.WriteLine(line, argv[i]); + } catch (const std::exception& e) { + if (kVerbose) { + std::string what = e.what(); + Detail::print_warning("exit because of: " + what, "CompilerKit"); + } + + try { + std::filesystem::remove(object_output); + } catch (...) { + } + + goto asm_fail_exit; + } + } + + if (!kOutputAsBinary) { + if (kVerbose) { + kStdOut << "AssemblerAMD64: Writing object file...\n"; + } + + // this is the final step, write everything to the file. + + auto pos = file_ptr_out.tellp(); + + hdr.fCount = kRecords.size() + kUndefinedSymbols.size(); + + file_ptr_out << hdr; + + if (kRecords.empty()) { + kStdErr << "AssemblerAMD64: At least one record is needed to write an object " + "file.\nAssemblerAMD64: Make one using `public_segment .code64 foo_bar`.\n"; + + std::filesystem::remove(object_output); + return 1; + } + + kRecords[kRecords.size() - 1].fSize = kAppBytes.size(); + + std::size_t record_count = 0UL; + + for (auto& rec : kRecords) { + if (kVerbose) kStdOut << "AssemblerAMD64: Wrote record " << rec.fName << " to file...\n"; + + rec.fFlags |= CompilerKit::kKindRelocationAtRuntime; + rec.fOffset = record_count; + ++record_count; + + file_ptr_out << rec; + } + + // increment once again, so that we won't lie about the kUndefinedSymbols. + ++record_count; + + for (auto& sym : kUndefinedSymbols) { + CompilerKit::AERecordHeader _record_hdr{0}; + + if (kVerbose) kStdOut << "AssemblerAMD64: Wrote symbol " << sym << " to file...\n"; + + _record_hdr.fKind = kAENullType; + _record_hdr.fSize = sym.size(); + _record_hdr.fOffset = record_count; + + ++record_count; + + memset(_record_hdr.fPad, kAENullType, kAEPad); + memcpy(_record_hdr.fName, sym.c_str(), sym.size()); + + file_ptr_out << _record_hdr; + + ++kCounter; + } + + auto pos_end = file_ptr_out.tellp(); + + file_ptr_out.seekp(pos); + + hdr.fStartCode = pos_end; + hdr.fCodeSize = kAppBytes.size(); + + file_ptr_out << hdr; + + file_ptr_out.seekp(pos_end); + } else { + if (kVerbose) { + kStdOut << "AssemblerAMD64: Write raw binary...\n"; + } + } + + // byte from byte, we write this. + for (auto& byte : kAppBytes) { + if (byte == 0) continue; + + if (byte == 0xFF) { + byte = 0; + } + + file_ptr_out << reinterpret_cast(&byte)[0]; + } + + if (kVerbose) kStdOut << "AssemblerAMD64: Wrote file with program in it.\n"; + + file_ptr_out.flush(); + file_ptr_out.close(); + + if (kVerbose) kStdOut << "AssemblerAMD64: Exit succeeded.\n"; + + return 0; + } + +asm_fail_exit: + + if (kVerbose) kStdOut << "AssemblerAMD64: Exit failed.\n"; + + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for attributes +// returns true if any was found. + +///////////////////////////////////////////////////////////////////////////////////////// + +static bool asm_read_attributes(std::string line) { + // extern_segment is the opposite of public_segment, it signals to the ld + // that we need this symbol. + if (CompilerKit::find_word(line, "extern_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_extern_segment_bin"); + } + + auto name = line.substr(line.find("extern_segment") + strlen("extern_segment") + 1); + + if (name.size() == 0) { + Detail::print_error("Invalid extern_segment", "power-as"); + throw std::runtime_error("invalid_extern_segment"); + } + + std::string result = std::to_string(name.size()); + result += kUndefinedSymbol; + + // mangle this + for (char& j : name) { + if (j == ' ' || j == ',') j = '$'; + } + + result += name; + + if (name.find(kPefCode64) != std::string::npos) { + // data is treated as code. + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(kPefData64) != std::string::npos) { + // no code will be executed from here. + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(kPefZero64) != std::string::npos) { + // this is a bss section. + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that ld can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kAppBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, result.c_str(), result.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + // public_segment is a special keyword used by AssemblerAMD64 to tell the AE output stage to + // mark this section as a header. it currently supports .code64, .data64 and + // .zero64. + else if (CompilerKit::find_word(line, "public_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_public_segment_bin"); + } + + auto name = line.substr(line.find("public_segment") + strlen("public_segment") + 1); + + std::string name_copy = name; + + for (char& j : name) { + if (j == ' ') j = '$'; + } + + if (std::find(kDefinedSymbols.begin(), kDefinedSymbols.end(), name) != kDefinedSymbols.end()) { + Detail::print_error("Symbol already defined.", "CompilerKit"); + throw std::runtime_error("invalid_public_segment_bin"); + } + + kDefinedSymbols.push_back(name); + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + + name_copy.erase(name_copy.find(".code64"), strlen(".code64")); + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + + name_copy.erase(name_copy.find(".data64"), strlen(".data64")); + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + + name_copy.erase(name_copy.find(".zero64"), strlen(".zero64")); + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that ld can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + while (name_copy.find(" ") != std::string::npos) name_copy.erase(name_copy.find(" "), 1); + + kOriginLabel.push_back(std::make_pair(name_copy, kOrigin)); + ++kOrigin; + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kAppBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, name.c_str(), name.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + + return false; +} + +// \brief algorithms and helpers. + +namespace Detail::algorithm { +// \brief authorize a brief set of characters. +static inline bool is_not_valid(char c) { + if ((isalpha(c) || isdigit(c)) || + ((c == ' ') || (c == '\t') || (c == ',') || (c == '(') || (c == ')') || (c == '"') || + (c == '*') || (c == '\'') || (c == '[') || (c == ']') || (c == '+') || (c == '_') || + (c == ':') || (c == '@') || (c == '.') || (c == '#') || (c == ';'))) + return false; + + return true; +} + +bool is_valid_amd64(std::string str) { + return std::find_if(str.begin(), str.end(), is_not_valid) == str.end(); +} +} // namespace Detail::algorithm + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for line (syntax check) + +///////////////////////////////////////////////////////////////////////////////////////// + +std::string CompilerKit::EncoderAMD64::CheckLine(std::string line, std::string file) { + std::string err_str; + + if (line.empty() || CompilerKit::find_word(line, "extern_segment") || + CompilerKit::find_word(line, "public_segment") || + CompilerKit::find_word(line, kAssemblerPragmaSymStr) || CompilerKit::find_word(line, ";") || + line[0] == kAssemblerPragmaSym) { + if (line.find(';') != std::string::npos) { + line.erase(line.find(';')); + } else { + // now check the line for validity + if (!Detail::algorithm::is_valid_amd64(line)) { + err_str = "Line contains non valid characters.\nhere -> "; + err_str += line; + } + } + + return err_str; + } + + // check for a valid instruction format. + + if (line.find(',') != std::string::npos) { + if (line.find(',') + 1 == line.size()) { + err_str += "\nInstruction lacks right register, here -> "; + err_str += line.substr(line.find(',')); + + return err_str; + } else { + bool nothing_on_right = true; + + if (line.find(',') + 1 > line.size()) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + + auto substr = line.substr(line.find(',') + 1); + + for (auto& ch : substr) { + if (ch != ' ' && ch != '\t') { + nothing_on_right = false; + } + } + + // this means we found nothing after that ',' . + if (nothing_on_right) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + } + } + for (auto& opcodeAMD64 : kOpcodesAMD64) { + if (CompilerKit::find_word(line, opcodeAMD64.fName)) { + return err_str; + } + } + + err_str += "\nUnrecognized instruction -> " + line; + + return err_str; +} + +bool CompilerKit::EncoderAMD64::WriteNumber(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + CompilerKit::NumberCast64 num = + CompilerKit::NumberCast64(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16)); + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 16 number here: " << jump_label.substr(pos) + << "\n"; + } + + return true; + } + case 'b': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + CompilerKit::NumberCast64 num = + CompilerKit::NumberCast64(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + return true; + } + case 'o': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + CompilerKit::NumberCast64 num = + CompilerKit::NumberCast64(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + return true; + } + default: { + break; + } + } + + /* check for errno and stuff like that */ + if (auto res = strtol(jump_label.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + return false; + } + } + + CompilerKit::NumberCast64 num = + CompilerKit::NumberCast64(strtol(jump_label.substr(pos).c_str(), nullptr, 10)); + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +bool CompilerKit::EncoderAMD64::WriteNumber32(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); + res += kOrigin; + + if (errno != 0) { + return false; + } + + CompilerKit::NumberCast32 num = CompilerKit::NumberCast32(res); + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 16 number here: " << jump_label.substr(pos) + << "\n"; + } + + return true; + } + case 'b': { + auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); + res += kOrigin; + + if (errno != 0) { + return false; + } + + CompilerKit::NumberCast32 num = CompilerKit::NumberCast32(res); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + return true; + } + case 'o': { + auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); + res += kOrigin; + + if (errno != 0) { + return false; + } + + CompilerKit::NumberCast32 num = CompilerKit::NumberCast32(res); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + return true; + } + default: { + break; + } + } + + auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 10); + res += kOrigin; + + if (errno != 0) { + return false; + } + + CompilerKit::NumberCast32 num = CompilerKit::NumberCast32(res); + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +bool CompilerKit::EncoderAMD64::WriteNumber16(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + CompilerKit::NumberCast16 num = + CompilerKit::NumberCast16(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16)); + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 16 number here: " << jump_label.substr(pos) + << "\n"; + } + + return true; + } + case 'b': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + CompilerKit::NumberCast16 num = + CompilerKit::NumberCast16(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + return true; + } + case 'o': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + CompilerKit::NumberCast16 num = + CompilerKit::NumberCast16(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + return true; + } + default: { + break; + } + } + + /* check for errno and stuff like that */ + if (auto res = strtol(jump_label.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + return false; + } + } + + CompilerKit::NumberCast16 num = + CompilerKit::NumberCast16(strtol(jump_label.substr(pos).c_str(), nullptr, 10)); + + for (char& i : num.number) { + if (i == 0) i = 0xFF; + + kAppBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +bool CompilerKit::EncoderAMD64::WriteNumber8(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + CompilerKit::NumberCast8 num = + CompilerKit::NumberCast8(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16)); + + kAppBytes.push_back(num.number); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 16 number here: " << jump_label.substr(pos) + << "\n"; + } + + return true; + } + case 'b': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + CompilerKit::NumberCast8 num = + CompilerKit::NumberCast8(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + kAppBytes.push_back(num.number); + + return true; + } + case 'o': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + CompilerKit::NumberCast8 num = + CompilerKit::NumberCast8(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + kAppBytes.push_back(num.number); + + return true; + } + default: { + break; + } + } + + /* check for errno and stuff like that */ + if (auto res = strtol(jump_label.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + return false; + } + } + + CompilerKit::NumberCast8 num = + CompilerKit::NumberCast8(strtol(jump_label.substr(pos).c_str(), nullptr, 10)); + + kAppBytes.push_back(num.number); + + if (kVerbose) { + kStdOut << "AssemblerAMD64: Found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Read and write an instruction to the output array. + +///////////////////////////////////////////////////////////////////////////////////////// + +bool CompilerKit::EncoderAMD64::WriteLine(std::string line, std::string file) { + if (CompilerKit::find_word(line, "public_segment ")) return true; + + struct RegMapAMD64 { + CompilerKit::STLString fName; + i64_byte_t fModRM; + }; + + std::vector kRegisterList{ + {.fName = "ax", .fModRM = 0x0}, {.fName = "cx", .fModRM = 1}, + {.fName = "dx", .fModRM = 0x2}, {.fName = "bx", .fModRM = 3}, + {.fName = "sp", .fModRM = 0x4}, {.fName = "bp", .fModRM = 5}, + {.fName = "si", .fModRM = 0x6}, {.fName = "di", .fModRM = 7}, + }; + + Bool foundInstruction = false; + + for (auto& opcodeAMD64 : kOpcodesAMD64) { + // strict check here + if (CompilerKit::find_word(line, opcodeAMD64.fName) && + Detail::algorithm::is_valid_amd64(line)) { + foundInstruction = true; + std::string name(opcodeAMD64.fName); + + /// Move instruction handler. + if (line.find(name) != std::string::npos) { + if (name == "mov" || name == "xor") { + std::string substr = line.substr(line.find(name) + name.size()); + + uint64_t bits = kRegisterBitWidth; + + if (substr.find(",") == std::string::npos) { + Detail::print_error("Syntax error: missing right operand.", "CompilerKit"); + throw std::runtime_error("syntax_err"); + } + + bool onlyOneReg = true; + + std::vector currentRegList; + + for (auto& reg : kRegisterList) { + std::vector regExt = {'e', 'r'}; + + for (auto& ext : regExt) { + std::string registerName; + + if (bits > 16) registerName.push_back(ext); + + registerName += reg.fName; + + while (line.find(registerName) != std::string::npos) { + line.erase(line.find(registerName), registerName.size()); + + if (bits == 16) { + if (registerName[0] == 'r') { + Detail::print_error("invalid size for register, current bit width is: " + + std::to_string(kRegisterBitWidth), + file); + throw std::runtime_error("invalid_reg_size"); + } + } + + currentRegList.push_back({.fName = registerName, .fModRM = reg.fModRM}); + } + } + } + + if (currentRegList.size() > 1) onlyOneReg = false; + + bool hasRBasedRegs = false; + + if (!onlyOneReg) { + /// very tricky to understand. + /// but this checks for a r8 through r15 register. + if (currentRegList[0].fName[0] == 'r' || currentRegList[1].fName[0] == 'r') { + if (isdigit(currentRegList[0].fName[1]) && isdigit(currentRegList[1].fName[1])) { + kAppBytes.emplace_back(0x4d); + hasRBasedRegs = true; + } else if (isdigit(currentRegList[0].fName[1]) || + isdigit(currentRegList[1].fName[1])) { + kAppBytes.emplace_back(0x4c); + hasRBasedRegs = true; + } + } + } + + if (name == "mov") { + if (bits == 64 || bits == 32) { + if (!hasRBasedRegs && bits >= 32) { + kAppBytes.emplace_back(opcodeAMD64.fOpcode); + } + + if (!onlyOneReg) kAppBytes.emplace_back(0x89); + } else if (bits == 16) { + if (hasRBasedRegs) { + Detail::print_error("Invalid combination of operands and registers.", + "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } else { + kAppBytes.emplace_back(0x66); + kAppBytes.emplace_back(0x89); + } + } + } else { + if (!hasRBasedRegs && bits >= 32) { + kAppBytes.emplace_back(opcodeAMD64.fOpcode); + } + + kAppBytes.emplace_back(0x31); + } + + if (onlyOneReg) { + auto num = GetNumber32(line, ","); + + for (auto& num_idx : num.number) { + if (num_idx == 0) num_idx = 0xFF; + } + + auto modrm = (0x3 << 6 | currentRegList[0].fModRM); + + kAppBytes.emplace_back(0xC7); // prefixed before placing the modrm and then the number. + kAppBytes.emplace_back(modrm); + + if (name != "xor") { + kAppBytes.emplace_back(num.number[0]); + kAppBytes.emplace_back(num.number[1]); + kAppBytes.emplace_back(num.number[2]); + kAppBytes.emplace_back(num.number[3]); + } + + break; + } + + if (currentRegList[1].fName[0] == 'r' && currentRegList[0].fName[0] == 'e') { + Detail::print_error("Invalid combination of operands and registers.", "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } + + if (currentRegList[0].fName[0] == 'r' && currentRegList[1].fName[0] == 'e') { + Detail::print_error("Invalid combination of operands and registers.", "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } + + if (bits == 16) { + if (currentRegList[0].fName[0] == 'r' || currentRegList[0].fName[0] == 'e') { + Detail::print_error("Invalid combination of operands and registers.", "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } + + if (currentRegList[1].fName[0] == 'r' || currentRegList[1].fName[0] == 'e') { + Detail::print_error("Invalid combination of operands and registers.", "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } + } else { + if (currentRegList[0].fName[0] != 'r' || currentRegList[0].fName[0] == 'e') { + Detail::print_error("Invalid combination of operands and registers.", "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } + + if (currentRegList[1].fName[0] != 'r' || currentRegList[1].fName[0] == 'e') { + Detail::print_error("Invalid combination of operands and registers.", "CompilerKit"); + throw std::runtime_error("comb_op_reg"); + } + } + + /// encode register using the modrm encoding. + + auto modrm = (0x3 << 6 | currentRegList[1].fModRM << 3 | currentRegList[0].fModRM); + + kAppBytes.emplace_back(modrm); + + break; + } + } + + if (name == "int" || name == "into" || name == "intd") { + kAppBytes.emplace_back(opcodeAMD64.fOpcode); + this->WriteNumber8(line.find(name) + name.size() + 1, line); + + break; + } else if (name == "jmp" || name == "call") { + kAppBytes.emplace_back(opcodeAMD64.fOpcode); + + if (!this->WriteNumber32(line.find(name) + name.size() + 1, line)) { + throw std::runtime_error("BUG: WriteNumber32"); + } + + break; + } else if (name == "syscall") { + kAppBytes.emplace_back(opcodeAMD64.fOpcode); + kAppBytes.emplace_back(0x05); + + break; + } else { + kAppBytes.emplace_back(opcodeAMD64.fOpcode); + + break; + } + } + } + + if (line[0] == kAssemblerPragmaSym) { + if (foundInstruction) { + Detail::print_error("Syntax error: " + line, file); + throw std::runtime_error("syntax_err"); + } + + if (line.find("bits 64") != std::string::npos) { + kRegisterBitWidth = 64U; + } else if (line.find("bits 32") != std::string::npos) { + kRegisterBitWidth = 32U; + } else if (line.find("bits 16") != std::string::npos) { + kRegisterBitWidth = 16U; + } else if (line.find("org") != std::string::npos) { + size_t base[] = {10, 16, 2, 7}; + + for (size_t i = 0; i < 4; i++) { + if (kOrigin = strtol((line.substr(line.find("org") + strlen("org") + 1)).c_str(), nullptr, + base[i]); + kOrigin) { + if (errno != 0) { + continue; + } else { + if (kVerbose) { + kStdOut << "AssemblerAMD64: Origin Set: " << kOrigin << std::endl; + } + + break; + } + } + } + } + } + /// write a dword + else if (line.find(".dword") != std::string::npos) { + this->WriteNumber32(line.find(".dword") + strlen(".dword") + 1, line); + } + /// write a long + else if (line.find(".long") != std::string::npos) { + this->WriteNumber(line.find(".long") + strlen(".long") + 1, line); + } + /// write a 16-bit number + else if (line.find(".word") != std::string::npos) { + this->WriteNumber16(line.find(".word") + strlen(".word") + 1, line); + } + + kOrigin += kIPAlignement; + + return true; +} + +// Last rev 13-1-24 diff --git a/src/CompilerKit/src/Backend/AssemblerARM64.cc b/src/CompilerKit/src/Backend/AssemblerARM64.cc new file mode 100644 index 0000000..4961e61 --- /dev/null +++ b/src/CompilerKit/src/Backend/AssemblerARM64.cc @@ -0,0 +1,587 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @file AssemblerARM64.cc +/// @author Amlal El Mahrouss +/// @brief 'ACORN' Assembler. + +/// REMINDER: when dealing with an undefined symbol use (string +/// size):LinkerFindSymbol:(string) so that li will look for it. + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __ASM_NEED_ARM64__ +#define __ASM_NEED_ARM64__ 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///////////////////// + +// ANSI ESCAPE CODES + +///////////////////// + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" +#define kYellow "\e[0;33m" + +constexpr auto cPowerIPAlignment = 0x1U; + +static Char kOutputArch = CompilerKit::kPefArchARM64; + +static std::size_t kCounter = 1UL; + +static std::uintptr_t kOrigin = kPefBaseOrigin; +static std::vector> kOriginLabel; + +static std::vector kBytes; + +static CompilerKit::AERecordHeader kCurrentRecord{ + .fName = "", .fKind = CompilerKit::kPefCode, .fSize = 0, .fOffset = 0}; + +static std::vector kRecords; +static std::vector kUndefinedSymbols; + +static const std::string kUndefinedSymbol = ":UndefinedSymbol:"; +static const std::string kRelocSymbol = ":RuntimeSymbol:"; + +// \brief forward decl. +static bool asm_read_attributes(std::string line); + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @brief POWER assembler entrypoint, the program/module starts here. + +///////////////////////////////////////////////////////////////////////////////////////// + +NECTI_MODULE(AssemblerMainARM64) { + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + + for (size_t i = 1; i < argc; ++i) { + if (argv[i][0] == '-') { + if (strcmp(argv[i], "--ver") == 0 || strcmp(argv[i], "--v") == 0) { + kStdOut << "AssemblerPower: AARCH64 Assembler Driver.\nAssemblerPower: " << kDistVersion + << "\nAssemblerPower: " + "Copyright (c) " + "Amlal El Mahrouss\n"; + return 0; + } else if (strcmp(argv[i], "--h") == 0) { + kStdOut << "AssemblerPower: AARCH64 Assembler Driver.\nAssemblerPower: Copyright (c) 2024 " + "Amlal El Mahrouss\n"; + kStdOut << "--version,/v: print program version.\n"; + kStdOut << "--verbose: print verbose output.\n"; + kStdOut << "--binary: output as flat binary.\n"; + + return 0; + } else if (strcmp(argv[i], "--binary") == 0) { + kOutputAsBinary = true; + continue; + } else if (strcmp(argv[i], "--verbose") == 0) { + kVerbose = true; + continue; + } + + kStdOut << "AssemblerPower: ignore " << argv[i] << "\n"; + continue; + } + + if (!std::filesystem::exists(argv[i])) { + kStdOut << "AssemblerPower: can't open: " << argv[i] << std::endl; + goto asm_fail_exit; + } + + std::string object_output(argv[i]); + + for (auto& ext : kAsmFileExts) { + if (object_output.find(ext) != std::string::npos) { + object_output.erase(object_output.find(ext), std::strlen(ext)); + } + } + + object_output += kOutputAsBinary ? kBinaryFileExt : kObjectFileExt; + + std::ifstream file_ptr(argv[i]); + std::ofstream file_ptr_out(object_output, std::ofstream::binary); + + if (file_ptr_out.bad()) { + if (kVerbose) { + kStdOut << "AssemblerPower: error: " << strerror(errno) << "\n"; + } + } + + std::string line; + + CompilerKit::AEHeader hdr{0}; + + memset(hdr.fPad, kAENullType, kAEPad); + + hdr.fMagic[0] = kAEMag0; + hdr.fMagic[1] = kAEMag1; + hdr.fSize = sizeof(CompilerKit::AEHeader); + hdr.fArch = kOutputArch; + + ///////////////////////////////////////////////////////////////////////////////////////// + + // COMPILATION LOOP + + ///////////////////////////////////////////////////////////////////////////////////////// + + CompilerKit::EncoderARM64 asm64; + + while (std::getline(file_ptr, line)) { + if (auto ln = asm64.CheckLine(line, argv[i]); !ln.empty()) { + Detail::print_error(ln, argv[i]); + continue; + } + + try { + asm_read_attributes(line); + asm64.WriteLine(line, argv[i]); + } catch (const std::exception& e) { + if (kVerbose) { + std::string what = e.what(); + Detail::print_warning("exit because of: " + what, "CompilerKit"); + } + + std::filesystem::remove(object_output); + goto asm_fail_exit; + } + } + + if (!kOutputAsBinary) { + if (kVerbose) { + kStdOut << "AssemblerARM64: Writing object file...\n"; + } + + // this is the final step, write everything to the file. + + auto pos = file_ptr_out.tellp(); + + hdr.fCount = kRecords.size() + kUndefinedSymbols.size(); + + file_ptr_out << hdr; + + if (kRecords.empty()) { + kStdErr << "AssemblerARM64: At least one record is needed to write an object " + "file.\nAssemblerARM64: Make one using `public_segment .code64 foo_bar`.\n"; + + std::filesystem::remove(object_output); + return 1; + } + + kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + std::size_t record_count = 0UL; + + for (auto& record_hdr : kRecords) { + record_hdr.fFlags |= CompilerKit::kKindRelocationAtRuntime; + record_hdr.fOffset = record_count; + ++record_count; + + file_ptr_out << record_hdr; + + if (kVerbose) kStdOut << "AssemblerARM64: Wrote record " << record_hdr.fName << "...\n"; + } + + // increment once again, so that we won't lie about the kUndefinedSymbols. + ++record_count; + + for (auto& sym : kUndefinedSymbols) { + CompilerKit::AERecordHeader undefined_sym{0}; + + if (kVerbose) kStdOut << "AssemblerARM64: Wrote symbol " << sym << " to file...\n"; + + undefined_sym.fKind = kAENullType; + undefined_sym.fSize = sym.size(); + undefined_sym.fOffset = record_count; + + ++record_count; + + memset(undefined_sym.fPad, kAENullType, kAEPad); + memcpy(undefined_sym.fName, sym.c_str(), sym.size()); + + file_ptr_out << undefined_sym; + + ++kCounter; + } + + auto pos_end = file_ptr_out.tellp(); + + file_ptr_out.seekp(pos); + + hdr.fStartCode = pos_end; + hdr.fCodeSize = kBytes.size(); + + file_ptr_out << hdr; + + file_ptr_out.seekp(pos_end); + } else { + if (kVerbose) { + kStdOut << "AssemblerARM64: Write raw binary...\n"; + } + } + + // byte from byte, we write this. + for (auto& byte : kBytes) { + file_ptr_out.write(reinterpret_cast(&byte), sizeof(byte)); + } + + if (kVerbose) kStdOut << "AssemblerARM64: Wrote file with program in it.\n"; + + file_ptr_out.flush(); + file_ptr_out.close(); + + if (kVerbose) kStdOut << "AssemblerARM64: Exit succeeded.\n"; + + return 0; + } + +asm_fail_exit: + + if (kVerbose) kStdOut << "AssemblerARM64: Exit failed.\n"; + + return NECTI_EXEC_ERROR; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for attributes +// returns true if any was found. + +///////////////////////////////////////////////////////////////////////////////////////// + +static bool asm_read_attributes(std::string line) { + // extern_segment is the opposite of public_segment, it signals to the li + // that we need this symbol. + if (CompilerKit::find_word(line, "extern_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid extern_segment directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_extern_segment_bin"); + } + + auto name = line.substr(line.find("extern_segment") + strlen("extern_segment") + 1); + + if (name.size() == 0) { + Detail::print_error("Invalid extern_segment", "CompilerKit"); + throw std::runtime_error("invalid_extern_segment"); + } + + std::string result = std::to_string(name.size()); + result += kUndefinedSymbol; + + // mangle this + for (char& j : name) { + if (j == ' ' || j == ',') j = '$'; + } + + result += name; + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that li can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, result.c_str(), result.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + // public_segment is a special keyword used by Assembler to tell the AE output stage to + // mark this section as a header. it currently supports .code64, .data64., + // .zero64 + else if (CompilerKit::find_word(line, "public_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid public_segment directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_public_segment_bin"); + } + + auto name = line.substr(line.find("public_segment") + strlen("public_segment")); + + std::string name_copy = name; + + for (char& j : name) { + if (j == ' ') j = '$'; + } + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + + name_copy.erase(name_copy.find(".code64"), strlen(".code64")); + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + + name_copy.erase(name_copy.find(".data64"), strlen(".data64")); + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + + name_copy.erase(name_copy.find(".zero64"), strlen(".zero64")); + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that li can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + while (name_copy.find(" ") != std::string::npos) name_copy.erase(name_copy.find(" "), 1); + + kOriginLabel.push_back(std::make_pair(name_copy, kOrigin)); + ++kOrigin; + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, name.c_str(), name.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + + return false; +} + +// \brief algorithms and helpers. + +namespace Detail::algorithm { +// \brief authorize a brief set of characters. +static inline bool is_not_alnum_space(char c) { + return !(isalpha(c) || isdigit(c) || (c == ' ') || (c == '\t') || (c == ',') || (c == '(') || + (c == ')') || (c == '"') || (c == '\'') || (c == '[') || (c == ']') || (c == '+') || + (c == '_') || (c == ':') || (c == '@') || (c == '.')); +} + +bool is_valid_arm64(std::string str) { + return std::find_if(str.begin(), str.end(), is_not_alnum_space) == str.end(); +} +} // namespace Detail::algorithm + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for line (syntax check) + +///////////////////////////////////////////////////////////////////////////////////////// + +std::string CompilerKit::EncoderARM64::CheckLine(std::string line, std::string file) { + std::string err_str; + + if (line.empty() || CompilerKit::find_word(line, "extern_segment") || + CompilerKit::find_word(line, "public_segment") || line.find('#') != std::string::npos || + CompilerKit::find_word(line, ";")) { + if (line.find('#') != std::string::npos) { + line.erase(line.find('#')); + } else if (line.find(';') != std::string::npos) { + line.erase(line.find(';')); + } else { + /// does the line contains valid input? + if (!Detail::algorithm::is_valid_arm64(line)) { + err_str = "Line contains non alphanumeric characters.\nhere -> "; + err_str += line; + } + } + + return err_str; + } + + if (!Detail::algorithm::is_valid_arm64(line)) { + err_str = "Line contains non alphanumeric characters.\nhere -> "; + err_str += line; + + return err_str; + } + + // check for a valid instruction format. + + if (line.find(',') != std::string::npos) { + if (line.find(',') + 1 == line.size()) { + err_str += "\nInstruction lacks right register, here -> "; + err_str += line.substr(line.find(',')); + + return err_str; + } else { + bool nothing_on_right = true; + + if (line.find(',') + 1 > line.size()) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + + auto substr = line.substr(line.find(',') + 1); + + for (auto& ch : substr) { + if (ch != ' ' && ch != '\t') { + nothing_on_right = false; + } + } + + // this means we found nothing after that ',' . + if (nothing_on_right) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + } + } + + return err_str; +} + +bool CompilerKit::EncoderARM64::WriteNumber(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16)); + + for (char& i : num.number) { + kBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerARM64: found a base 16 number here: " << jump_label.substr(pos) + << "\n"; + } + + return true; + } + case 'b': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "AssemblerARM64: found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + kBytes.push_back(i); + } + + return true; + } + case 'o': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "AssemblerARM64: found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + kBytes.push_back(i); + } + + return true; + } + default: { + break; + } + } + + /* check for errno and stuff like that */ + if (auto res = strtol(jump_label.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + return false; + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos).c_str(), nullptr, 10)); + + for (char& i : num.number) { + kBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerARM64: found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @brief Read and write an instruction to the output array. + +///////////////////////////////////////////////////////////////////////////////////////// + +bool CompilerKit::EncoderARM64::WriteLine(std::string line, std::string file) { + if (CompilerKit::find_word(line, "public_segment")) return false; + + if (!Detail::algorithm::is_valid_arm64(line)) return false; + + return true; +} + +// Last rev 13-1-24 diff --git a/src/CompilerKit/src/Backend/AssemblerPowerPC.cc b/src/CompilerKit/src/Backend/AssemblerPowerPC.cc new file mode 100644 index 0000000..b4f14ea --- /dev/null +++ b/src/CompilerKit/src/Backend/AssemblerPowerPC.cc @@ -0,0 +1,911 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @file AssemblerPower.cc +/// @author Amlal El Mahrouss +/// @brief POWER Assembler. + +/// REMINDER: when dealing with an undefined symbol use (string +/// size):LinkerFindSymbol:(string) so that li will look for it. + +///////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __ASM_NEED_PPC__ +#define __ASM_NEED_PPC__ 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///////////////////// + +// ANSI ESCAPE CODES + +///////////////////// + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" +#define kYellow "\e[0;33m" + +constexpr auto cPowerIPAlignment = 0x4U; + +static Char kOutputArch = CompilerKit::kPefArchPowerPC; + +static std::size_t kCounter = 1UL; + +static std::uintptr_t kOrigin = kPefBaseOrigin; +static std::vector> kOriginLabel; + +static std::vector kBytes; + +static CompilerKit::AERecordHeader kCurrentRecord{ + .fName = "", .fKind = CompilerKit::kPefCode, .fSize = 0, .fOffset = 0}; + +static std::vector kRecords; +static std::vector kUndefinedSymbols; + +static const std::string kUndefinedSymbol = ":UndefinedSymbol:"; +static const std::string kRelocSymbol = ":RuntimeSymbol:"; + +// \brief forward decl. +static bool asm_read_attributes(std::string line); + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @brief POWER assembler entrypoint, the program/module starts here. + +///////////////////////////////////////////////////////////////////////////////////////// + +NECTI_MODULE(AssemblerMainPower64) { + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + + for (size_t i = 1; i < argc; ++i) { + if (argv[i][0] == '-') { + if (strcmp(argv[i], "--ver") == 0 || strcmp(argv[i], "--v") == 0) { + kStdOut << "AssemblerPower: POWER64 Assembler Driver.\nAssemblerPower: " << kDistVersion + << "\nAssemblerPower: " + "Copyright (c) " + "Amlal El Mahrouss\n"; + return 0; + } else if (strcmp(argv[i], "--h") == 0) { + kStdOut << "AssemblerPower: POWER64 Assembler Driver.\nAssemblerPower: Copyright (c) 2024 " + "Amlal El Mahrouss\n"; + kStdOut << "--version,/v: print program version.\n"; + kStdOut << "--verbose: print verbose output.\n"; + kStdOut << "--binary: output as flat binary.\n"; + + return 0; + } else if (strcmp(argv[i], "--binary") == 0) { + kOutputAsBinary = true; + continue; + } else if (strcmp(argv[i], "--verbose") == 0) { + kVerbose = true; + continue; + } + + kStdOut << "AssemblerPower: ignore " << argv[i] << "\n"; + continue; + } + + if (!std::filesystem::exists(argv[i])) { + kStdOut << "AssemblerPower: can't open: " << argv[i] << std::endl; + goto asm_fail_exit; + } + + std::string object_output(argv[i]); + + for (auto& ext : kAsmFileExts) { + if (object_output.find(ext) != std::string::npos) { + object_output.erase(object_output.find(ext), std::strlen(ext)); + } + } + + object_output += kOutputAsBinary ? kBinaryFileExt : kObjectFileExt; + + std::ifstream file_ptr(argv[i]); + std::ofstream file_ptr_out(object_output, std::ofstream::binary); + + if (file_ptr_out.bad()) { + if (kVerbose) { + kStdOut << "AssemblerPower: error: " << strerror(errno) << "\n"; + } + } + + std::string line; + + CompilerKit::AEHeader hdr{0}; + + memset(hdr.fPad, kAENullType, kAEPad); + + hdr.fMagic[0] = kAEMag0; + hdr.fMagic[1] = kAEMag1; + hdr.fSize = sizeof(CompilerKit::AEHeader); + hdr.fArch = kOutputArch; + + ///////////////////////////////////////////////////////////////////////////////////////// + + // COMPILATION LOOP + + ///////////////////////////////////////////////////////////////////////////////////////// + + CompilerKit::EncoderPowerPC asm64; + + while (std::getline(file_ptr, line)) { + if (auto ln = asm64.CheckLine(line, argv[i]); !ln.empty()) { + Detail::print_error(ln, argv[i]); + continue; + } + + try { + asm_read_attributes(line); + asm64.WriteLine(line, argv[i]); + } catch (const std::exception& e) { + if (kVerbose) { + std::string what = e.what(); + Detail::print_warning("exit because of: " + what, "CompilerKit"); + } + + std::filesystem::remove(object_output); + goto asm_fail_exit; + } + } + + if (!kOutputAsBinary) { + if (kVerbose) { + kStdOut << "AssemblerPower: Writing object file...\n"; + } + + // this is the final step, write everything to the file. + + auto pos = file_ptr_out.tellp(); + + hdr.fCount = kRecords.size() + kUndefinedSymbols.size(); + + file_ptr_out << hdr; + + if (kRecords.empty()) { + kStdErr << "AssemblerPower: At least one record is needed to write an object " + "file.\nAssemblerPower: Make one using `public_segment .code64 foo_bar`.\n"; + + std::filesystem::remove(object_output); + return 1; + } + + kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + std::size_t record_count = 0UL; + + for (auto& record_hdr : kRecords) { + record_hdr.fFlags |= CompilerKit::kKindRelocationAtRuntime; + record_hdr.fOffset = record_count; + ++record_count; + + file_ptr_out << record_hdr; + + if (kVerbose) kStdOut << "AssemblerPower: Wrote record " << record_hdr.fName << "...\n"; + } + + // increment once again, so that we won't lie about the kUndefinedSymbols. + ++record_count; + + for (auto& sym : kUndefinedSymbols) { + CompilerKit::AERecordHeader undefined_sym{0}; + + if (kVerbose) kStdOut << "AssemblerPower: Wrote symbol " << sym << " to file...\n"; + + undefined_sym.fKind = kAENullType; + undefined_sym.fSize = sym.size(); + undefined_sym.fOffset = record_count; + + ++record_count; + + memset(undefined_sym.fPad, kAENullType, kAEPad); + memcpy(undefined_sym.fName, sym.c_str(), sym.size()); + + file_ptr_out << undefined_sym; + + ++kCounter; + } + + auto pos_end = file_ptr_out.tellp(); + + file_ptr_out.seekp(pos); + + hdr.fStartCode = pos_end; + hdr.fCodeSize = kBytes.size(); + + file_ptr_out << hdr; + + file_ptr_out.seekp(pos_end); + } else { + if (kVerbose) { + kStdOut << "AssemblerPower: Write raw binary...\n"; + } + } + + // byte from byte, we write this. + for (auto& byte : kBytes) { + file_ptr_out.write(reinterpret_cast(&byte), sizeof(byte)); + } + + if (kVerbose) kStdOut << "AssemblerPower: Wrote file with program in it.\n"; + + file_ptr_out.flush(); + file_ptr_out.close(); + + if (kVerbose) kStdOut << "AssemblerPower: Exit succeeded.\n"; + + return 0; + } + +asm_fail_exit: + + if (kVerbose) kStdOut << "AssemblerPower: Exit failed.\n"; + + return NECTI_EXEC_ERROR; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for attributes +// returns true if any was found. + +///////////////////////////////////////////////////////////////////////////////////////// + +static bool asm_read_attributes(std::string line) { + // extern_segment is the opposite of public_segment, it signals to the li + // that we need this symbol. + if (CompilerKit::find_word(line, "extern_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid extern_segment directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_extern_segment_bin"); + } + + auto name = line.substr(line.find("extern_segment") + strlen("extern_segment") + 1); + + if (name.size() == 0) { + Detail::print_error("Invalid extern_segment", "CompilerKit"); + throw std::runtime_error("invalid_extern_segment"); + } + + std::string result = std::to_string(name.size()); + result += kUndefinedSymbol; + + // mangle this + for (char& j : name) { + if (j == ' ' || j == ',') j = '$'; + } + + result += name; + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that li can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, result.c_str(), result.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + // public_segment is a special keyword used by AssemblerPower to tell the AE output stage to + // mark this section as a header. it currently supports .code64, .data64., + // .zero64 + else if (CompilerKit::find_word(line, "public_segment")) { + if (kOutputAsBinary) { + Detail::print_error("Invalid public_segment directive in flat binary mode.", "CompilerKit"); + throw std::runtime_error("invalid_public_segment_bin"); + } + + auto name = line.substr(line.find("public_segment") + strlen("public_segment")); + + std::string name_copy = name; + + for (char& j : name) { + if (j == ' ') j = '$'; + } + + if (name.find(".code64") != std::string::npos) { + // data is treated as code. + + name_copy.erase(name_copy.find(".code64"), strlen(".code64")); + kCurrentRecord.fKind = CompilerKit::kPefCode; + } else if (name.find(".data64") != std::string::npos) { + // no code will be executed from here. + + name_copy.erase(name_copy.find(".data64"), strlen(".data64")); + kCurrentRecord.fKind = CompilerKit::kPefData; + } else if (name.find(".zero64") != std::string::npos) { + // this is a bss section. + + name_copy.erase(name_copy.find(".zero64"), strlen(".zero64")); + kCurrentRecord.fKind = CompilerKit::kPefZero; + } + + // this is a special case for the start stub. + // we want this so that li can find it. + + if (name == kPefStart) { + kCurrentRecord.fKind = CompilerKit::kPefCode; + } + + while (name_copy.find(" ") != std::string::npos) name_copy.erase(name_copy.find(" "), 1); + + kOriginLabel.push_back(std::make_pair(name_copy, kOrigin)); + ++kOrigin; + + // now we can tell the code size of the previous kCurrentRecord. + + if (!kRecords.empty()) kRecords[kRecords.size() - 1].fSize = kBytes.size(); + + memset(kCurrentRecord.fName, 0, kAESymbolLen); + memcpy(kCurrentRecord.fName, name.c_str(), name.size()); + + ++kCounter; + + memset(kCurrentRecord.fPad, kAENullType, kAEPad); + + kRecords.emplace_back(kCurrentRecord); + + return true; + } + + return false; +} + +// \brief algorithms and helpers. + +namespace Detail::algorithm { +// \brief authorize a brief set of characters. +static inline bool is_not_alnum_space(char c) { + return !(isalpha(c) || isdigit(c) || (c == ' ') || (c == '\t') || (c == ',') || (c == '(') || + (c == ')') || (c == '"') || (c == '\'') || (c == '[') || (c == ']') || (c == '+') || + (c == '_') || (c == ':') || (c == '@') || (c == '.')); +} + +bool is_valid_power64(std::string str) { + return std::find_if(str.begin(), str.end(), is_not_alnum_space) == str.end(); +} +} // namespace Detail::algorithm + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Check for line (syntax check) + +///////////////////////////////////////////////////////////////////////////////////////// + +std::string CompilerKit::EncoderPowerPC::CheckLine(std::string line, std::string file) { + std::string err_str; + + if (line.empty() || CompilerKit::find_word(line, "extern_segment") || + CompilerKit::find_word(line, "public_segment") || line.find('#') != std::string::npos || + CompilerKit::find_word(line, ";")) { + if (line.find('#') != std::string::npos) { + line.erase(line.find('#')); + } else if (line.find(';') != std::string::npos) { + line.erase(line.find(';')); + } else { + /// does the line contains valid input? + if (!Detail::algorithm::is_valid_power64(line)) { + err_str = "Line contains non alphanumeric characters.\nhere -> "; + err_str += line; + } + } + + return err_str; + } + + if (!Detail::algorithm::is_valid_power64(line)) { + err_str = "Line contains non alphanumeric characters.\nhere -> "; + err_str += line; + + return err_str; + } + + // check for a valid instruction format. + + if (line.find(',') != std::string::npos) { + if (line.find(',') + 1 == line.size()) { + err_str += "\nInstruction lacks right register, here -> "; + err_str += line.substr(line.find(',')); + + return err_str; + } else { + bool nothing_on_right = true; + + if (line.find(',') + 1 > line.size()) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + + auto substr = line.substr(line.find(',') + 1); + + for (auto& ch : substr) { + if (ch != ' ' && ch != '\t') { + nothing_on_right = false; + } + } + + // this means we found nothing after that ',' . + if (nothing_on_right) { + err_str += "\nInstruction not complete, here -> "; + err_str += line; + + return err_str; + } + } + } + + // these do take an argument. + std::vector operands_inst = {"stw", "li"}; + + // these don't. + std::vector filter_inst = {"blr", "bl", "sc"}; + + for (auto& opcode_risc : kOpcodesPowerPC) { + if (CompilerKit::find_word(line, opcode_risc.name)) { + for (auto& op : operands_inst) { + // if only the instruction was found. + if (line == op) { + err_str += "\nMalformed "; + err_str += op; + err_str += " instruction, here -> "; + err_str += line; + } + } + + // if it is like that -> addr1, 0x0 + if (auto it = std::find(filter_inst.begin(), filter_inst.end(), opcode_risc.name); + it == filter_inst.cend()) { + if (CompilerKit::find_word(line, opcode_risc.name)) { + if (!isspace(line[line.find(opcode_risc.name) + strlen(opcode_risc.name)])) { + err_str += "\nMissing space between "; + err_str += opcode_risc.name; + err_str += " and operands.\nhere -> "; + err_str += line; + } + } + } + + return err_str; + } + } + + err_str += "Unrecognized instruction: " + line; + + return err_str; +} + +bool CompilerKit::EncoderPowerPC::WriteNumber(const std::size_t& pos, std::string& jump_label) { + if (!isdigit(jump_label[pos])) return false; + + switch (jump_label[pos + 1]) { + case 'x': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 16)); + + for (char& i : num.number) { + kBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerPower: found a base 16 number here: " << jump_label.substr(pos) + << "\n"; + } + + return true; + } + case 'b': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "AssemblerPower: found a base 2 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + kBytes.push_back(i); + } + + return true; + } + case 'o': { + if (auto res = strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + jump_label, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos + 2).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "AssemblerPower: found a base 8 number here: " << jump_label.substr(pos) << "\n"; + } + + for (char& i : num.number) { + kBytes.push_back(i); + } + + return true; + } + default: { + break; + } + } + + /* check for errno and stuff like that */ + if (auto res = strtol(jump_label.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + return false; + } + } + + CompilerKit::NumberCast64 num(strtol(jump_label.substr(pos).c_str(), nullptr, 10)); + + for (char& i : num.number) { + kBytes.push_back(i); + } + + if (kVerbose) { + kStdOut << "AssemblerPower: found a base 10 number here: " << jump_label.substr(pos) << "\n"; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @brief Read and write an instruction to the output array. + +///////////////////////////////////////////////////////////////////////////////////////// + +bool CompilerKit::EncoderPowerPC::WriteLine(std::string line, std::string file) { + if (CompilerKit::find_word(line, "public_segment")) return false; + if (!Detail::algorithm::is_valid_power64(line)) return false; + + for (auto& opcode_risc : kOpcodesPowerPC) { + // strict check here + if (CompilerKit::find_word(line, opcode_risc.name)) { + std::string name(opcode_risc.name); + std::string jump_label, cpy_jump_label; + std::vector found_registers_index; + + // check funct7 type. + switch (opcode_risc.ops->type) { + default: { + NumberCast32 num(opcode_risc.opcode); + + for (auto ch : num.number) { + kBytes.emplace_back(ch); + } + break; + } + case BADDR: + case PCREL: { + auto num = GetNumber32(line, name); + + kBytes.emplace_back(num.number[0]); + kBytes.emplace_back(num.number[1]); + kBytes.emplace_back(num.number[2]); + kBytes.emplace_back(0x48); + + break; + } + /// General purpose, float, vector operations. Everything that involve + /// registers. + case G0REG: + case FREG: + case VREG: + case GREG: { + // \brief how many registers we found. + std::size_t found_some_count = 0UL; + std::size_t register_count = 0UL; + std::string opcodeName = opcode_risc.name; + std::size_t register_sum = 0; + + NumberCast64 num(opcode_risc.opcode); + + for (size_t line_index = 0UL; line_index < line.size(); line_index++) { + if (line[line_index] == kAsmRegisterPrefix[0] && isdigit(line[line_index + 1])) { + std::string register_syntax = kAsmRegisterPrefix; + register_syntax += line[line_index + 1]; + + if (isdigit(line[line_index + 2])) register_syntax += line[line_index + 2]; + + std::string reg_str; + reg_str += line[line_index + 1]; + + if (isdigit(line[line_index + 2])) reg_str += line[line_index + 2]; + + // it ranges from r0 to r19 + // something like r190 doesn't exist in the instruction set. + if (isdigit(line[line_index + 3]) && isdigit(line[line_index + 2])) { + reg_str += line[line_index + 3]; + Detail::print_error("invalid register index, r" + reg_str + + "\nnote: The POWER accepts registers from r0 to r32.", + file); + throw std::runtime_error("invalid_register_index"); + } + + // finally cast to a size_t + std::size_t reg_index = strtol(reg_str.c_str(), nullptr, 10); + + if (reg_index > kAsmRegisterLimit) { + Detail::print_error("invalid register index, r" + reg_str, file); + throw std::runtime_error("invalid_register_index"); + } + + if (opcodeName == "li") { + char numIndex = 0; + + for (size_t i = 0; i != reg_index; i++) { + numIndex += 0x20; + } + + auto num = GetNumber32(line, reg_str); + + kBytes.push_back(num.number[0]); + kBytes.push_back(num.number[1]); + kBytes.push_back(numIndex); + kBytes.push_back(0x38); + + // check if bigger than two. + for (size_t i = 2; i < 4; i++) { + if (num.number[i] > 0) { + Detail::print_warning("number overflow on li operation.", file); + break; + } + } + + break; + } + + if ((opcodeName[0] == 's' && opcodeName[1] == 't')) { + if (register_sum == 0) { + for (size_t indexReg = 0UL; indexReg < reg_index; ++indexReg) { + register_sum += 0x20; + } + } else { + register_sum += reg_index; + } + } + + if (opcodeName == "mr") { + switch (register_count) { + case 0: { + kBytes.push_back(0x78); + + char numIndex = 0x3; + + for (size_t i = 0; i != reg_index; i++) { + numIndex += 0x8; + } + + kBytes.push_back(numIndex); + + break; + } + case 1: { + char numIndex = 0x1; + + for (size_t i = 0; i != reg_index; i++) { + numIndex += 0x20; + } + + for (size_t i = 0; i != reg_index; i++) { + kBytes[kBytes.size() - 1] += 0x8; + } + + kBytes[kBytes.size() - 1] -= 0x8; + + kBytes.push_back(numIndex); + + if (reg_index >= 10 && reg_index < 20) + kBytes.push_back(0x7d); + else if (reg_index >= 20 && reg_index < 30) + kBytes.push_back(0x7e); + else if (reg_index >= 30) + kBytes.push_back(0x7f); + else + kBytes.push_back(0x7c); + + break; + } + default: + break; + } + + ++register_count; + ++found_some_count; + } + + if (opcodeName == "addi") { + if (found_some_count == 2 || found_some_count == 0) + kBytes.emplace_back(reg_index); + else if (found_some_count == 1) + kBytes.emplace_back(0x00); + + ++found_some_count; + + if (found_some_count > 3) { + Detail::print_error("Too much registers. -> " + line, file); + throw std::runtime_error("too_much_regs"); + } + } + + if (opcodeName.find("cmp") != std::string::npos) { + ++found_some_count; + + if (found_some_count > 3) { + Detail::print_error("Too much registers. -> " + line, file); + throw std::runtime_error("too_much_regs"); + } + } + + if (opcodeName.find("mf") != std::string::npos || + opcodeName.find("mt") != std::string::npos) { + char numIndex = 0; + + for (size_t i = 0; i != reg_index; i++) { + numIndex += 0x20; + } + + num.number[2] += numIndex; + + ++found_some_count; + + if (found_some_count > 1) { + Detail::print_error("Too much registers. -> " + line, file); + throw std::runtime_error("too_much_regs"); + } + + if (kVerbose) { + kStdOut << "AssemblerPower: Found register: " << register_syntax << "\n"; + kStdOut << "AssemblerPower: Amount of registers in instruction: " + << found_some_count << "\n"; + } + + if (reg_index >= 10 && reg_index < 20) + num.number[3] = 0x7d; + else if (reg_index >= 20 && reg_index < 30) + num.number[3] = 0x7e; + else if (reg_index >= 30) + num.number[3] = 0x7f; + else + num.number[3] = 0x7c; + + for (auto ch : num.number) { + kBytes.emplace_back(ch); + } + } + + found_registers_index.push_back(reg_index); + } + } + + if (opcodeName == "addi") { + kBytes.emplace_back(0x38); + } + + if (opcodeName.find("cmp") != std::string::npos) { + char rightReg = 0x0; + + for (size_t i = 0; i != found_registers_index[1]; i++) { + rightReg += 0x08; + } + + kBytes.emplace_back(0x00); + kBytes.emplace_back(rightReg); + kBytes.emplace_back(found_registers_index[0]); + kBytes.emplace_back(0x7c); + } + + if ((opcodeName[0] == 's' && opcodeName[1] == 't')) { + size_t offset = 0UL; + + if (line.find('+') != std::string::npos) { + auto number = GetNumber32(line.substr(line.find("+")), "+"); + offset = number.raw; + } + + kBytes.push_back(offset); + kBytes.push_back(0x00); + kBytes.push_back(register_sum); + + kBytes.emplace_back(0x90); + } + + if (opcodeName == "mr") { + if (register_count == 1) { + Detail::print_error("Too few registers. -> " + line, file); + throw std::runtime_error("too_few_registers"); + } + } + + // we're not in immediate addressing, reg to reg. + if (opcode_risc.ops->type != GREG) { + // remember! register to register! + if (found_some_count == 1) { + Detail::print_error( + "Unrecognized register found.\ntip: each AssemblerPower register " + "starts with 'r'.\nline: " + + line, + file); + + throw std::runtime_error("not_a_register"); + } + } + + if (found_some_count < 1 && name[0] != 'l' && name[0] != 's') { + Detail::print_error("invalid combination of opcode and registers.\nline: " + line, + file); + throw std::runtime_error("invalid_comb_op_reg"); + } + + break; + } + } + + kOrigin += cPowerIPAlignment; + break; + } + } + + return true; +} + +// Last rev 13-1-24 diff --git a/src/CompilerKit/src/Frontend.cc b/src/CompilerKit/src/Frontend.cc new file mode 100644 index 0000000..baac34e --- /dev/null +++ b/src/CompilerKit/src/Frontend.cc @@ -0,0 +1,61 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#include + +/** + * @file Frontend.cc + * @author Amlal El Mahrouss (amlal@nekernel.org) + * @brief Frontend API of NeCTI + * @version 0.0.2 + * + * @copyright Copyright (c) 2025 Amlal El Mahrouss and NeKernel.org Contributors + * + */ + +namespace CompilerKit { +/// find the perfect matching word in a haystack. +/// \param haystack base string +/// \param needle the string we search for. +/// \return if we found it or not. +Bool find_word(STLString haystack, STLString needle) noexcept { + auto index = haystack.find(needle); + + // check for needle validity. + if (index == STLString::npos) return false; + + // declare lambda + auto not_part_of_word = [&](int index) { + if (std::isspace(haystack[index]) || std::ispunct(haystack[index])) return true; + + if (index <= 0 || index >= haystack.size()) return true; + + return false; + }; + + return not_part_of_word(index - 1) && not_part_of_word(index + needle.size()); +} + +/// find a word within strict conditions and returns a range of it. +/// \param haystack +/// \param needle +/// \return position of needle. +SizeType find_word_range(STLString haystack, STLString needle) noexcept { + auto index = haystack.find(needle); + + // check for needle validity. + if (index == STLString::npos) return false; + + if (!isalnum((haystack[index + needle.size() + 1])) && + !isdigit(haystack[index + needle.size() + 1]) && + !isalnum((haystack[index - needle.size() - 1])) && + !isdigit(haystack[index - needle.size() - 1])) { + return index; + } + + return STLString::npos; +} +} // namespace CompilerKit \ No newline at end of file diff --git a/src/CompilerKit/src/Frontend/CCompiler64x0.cc b/src/CompilerKit/src/Frontend/CCompiler64x0.cc new file mode 100644 index 0000000..f07fcb0 --- /dev/null +++ b/src/CompilerKit/src/Frontend/CCompiler64x0.cc @@ -0,0 +1,1287 @@ +/* + * ======================================================== + * + * cc + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +/// BUGS: 0 +/// TODO: none + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* C driver */ +/* This is part of the CompilerKit. */ +/* (c) Amlal El Mahrouss */ + +/// @author Amlal El Mahrouss (amlal@nekernel.org) +/// @file 64x0-cc.cc +/// @brief 64x0 C Compiler. + +/// TODO: support structures, else if, else, . and -> + +///////////////////// + +// ANSI ESCAPE CODES + +///////////////////// + +#define kExitOK (0) + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" + +///////////////////////////////////// + +// INTERNAL STUFF OF THE C COMPILER + +///////////////////////////////////// + +namespace Detail { +// \brief Register map structure, used to keep track of each variable's registers. +struct CompilerRegisterMap final { + std::string fName; + std::string fReg; +}; + +// \brief Map for C structs +// \author amlal@nekernel.org +struct CompilerStructMap final { + // 'my_foo' + std::string fName; + + // if instance: stores a valid register. + std::string fReg; + + // offset count + std::size_t fOffsetsCnt; + + // offset array. + std::vector> fOffsets; +}; + +struct CompilerState final { + std::vector fSyntaxTreeList; + std::vector kStackFrame; + std::vector kStructMap; + CompilerKit::SyntaxLeafList* fSyntaxTree{nullptr}; + std::unique_ptr fOutputAssembly; + std::string fLastFile; + std::string fLastError; + bool fVerbose; +}; +} // namespace Detail + +static Detail::CompilerState kState; +static std::string kIfFunction = ""; + +namespace Detail { +/// @brief prints an error into stdout. +/// @param reason the reason of the error. +/// @param file where does it originate from? +void print_error(std::string reason, std::string file) noexcept; + +struct CompilerType final { + std::string fName; + std::string fValue; +}; +} // namespace Detail + +///////////////////////////////////////////////////////////////////////////////////////// + +// Target architecture. +static int kMachine = 0; + +///////////////////////////////////////// + +// REGISTERS ACCORDING TO USED ASSEMBLER + +///////////////////////////////////////// + +static size_t kRegisterCnt = kAsmRegisterLimit; +static size_t kStartUsable = 2; +static size_t kUsableLimit = 15; +static size_t kRegisterCounter = kStartUsable; +static std::string kRegisterPrefix = kAsmRegisterPrefix; + +///////////////////////////////////////// + +// COMPILER PARSING UTILITIES/STATES. + +///////////////////////////////////////// + +static std::vector kFileList; +static CompilerKit::AssemblyFactory kFactory; +static bool kInStruct = false; +static bool kOnWhileLoop = false; +static bool kOnForLoop = false; +static bool kInBraces = false; +static bool kIfFound = false; +static size_t kBracesCount = 0UL; + +/* @brief C compiler backend for C */ +class CompilerFrontend64x0 final : public CompilerKit::CompilerFrontendInterface { + public: + explicit CompilerFrontend64x0() = default; + ~CompilerFrontend64x0() override = default; + + NECTI_COPY_DEFAULT(CompilerFrontend64x0); + + std::string Check(const char* text, const char* file); + CompilerKit::SyntaxLeafList::SyntaxLeaf Compile(std::string text, std::string file) override; + + const char* Language() override { return "64k C"; } +}; + +static CompilerFrontend64x0* kCompilerFrontend = nullptr; +static std::vector kCompilerVariables; +static std::vector kCompilerFunctions; +static std::vector kCompilerTypes; + +namespace Detail { +union number_cast final { + public: + number_cast(UInt64 _Raw) : _Raw(_Raw) {} + + public: + char _Num[8]; + UInt64 _Raw; +}; + +union double_cast final { + public: + double_cast(float _Raw) : _Raw(_Raw) {} + + public: + char _Sign; + char _Lh[8]; + char _Rh[23]; + + float _Raw; +}; +} // namespace Detail + +///////////////////////////////////////////////////////////////////////////////////////// + +// @name Compile +// @brief Generate MASM from a C assignement. + +///////////////////////////////////////////////////////////////////////////////////////// + +CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontend64x0::Compile(std::string text_, + std::string file) { + std::string text = text_; + + bool typeFound = false; + bool fnFound = false; + + // setup generator. + std::random_device rd; + + auto seed_data = std::array{}; + std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + // start parsing + for (size_t text_index = 0; text_index < text.size(); ++text_index) { + auto syntaxLeaf = CompilerKit::SyntaxLeafList::SyntaxLeaf(); + + auto gen = uuids::uuid_random_generator{generator}; + uuids::uuid out = gen(); + + Detail::number_cast time_off = (UInt64) out.as_bytes().data(); + + if (!typeFound) { + auto substr = text.substr(text_index); + std::string match_type; + + for (size_t y = 0; y < substr.size(); ++y) { + if (substr[y] == ' ') { + while (match_type.find(' ') != std::string::npos) { + match_type.erase(match_type.find(' ')); + } + + for (auto& clType : kCompilerTypes) { + if (clType.fName == match_type) { + match_type.clear(); + + std::string buf; + + buf += clType.fValue; + buf += ' '; + + if (substr.find('=') != std::string::npos) { + break; + } + + if (text.find('(') != std::string::npos) { + syntaxLeaf.fUserValue = buf; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + } + + typeFound = true; + break; + } + } + + break; + } + + match_type += substr[y]; + } + } + + if (text[text_index] == '{') { + if (kInStruct) { + continue; + } + + kInBraces = true; + ++kBracesCount; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + } + + // return keyword handler + if (text[text_index] == 'r') { + std::string return_keyword; + return_keyword += "return"; + + std::size_t index = 0UL; + + std::string value; + + for (size_t return_index = text_index; return_index < text.size(); ++return_index) { + if (text[return_index] != return_keyword[index]) { + for (size_t value_index = return_index; value_index < text.size(); ++value_index) { + if (text[value_index] == ';') break; + + value += text[value_index]; + } + + break; + } + + ++index; + } + + if (index == return_keyword.size()) { + if (!value.empty()) { + if (value.find('(') != std::string::npos) { + value.erase(value.find('(')); + } + + if (!isdigit(value[value.find('(') + 2])) { + std::string tmp = value; + bool reg_to_reg = false; + + value.clear(); + + value += " extern_segment"; + value += tmp; + } + + syntaxLeaf.fUserValue = "\tldw r19, "; + + // make it pretty. + if (value.find('\t') != std::string::npos) value.erase(value.find('\t'), 1); + + syntaxLeaf.fUserValue += value + "\n"; + } + + syntaxLeaf.fUserValue += "\tjlr"; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + break; + } + } + + if (text[text_index] == 'i' && text[text_index + 1] == 'f') { + auto expr = text.substr(text_index + 2); + text.erase(text_index, 2); + + if (expr.find("{") != std::string::npos) { + expr.erase(expr.find("{")); + } + + if (expr.find("(") != std::string::npos) expr.erase(expr.find("(")); + + if (expr.find(")") != std::string::npos) expr.erase(expr.find(")")); + + kIfFunction = "__NECTI_IF_PROC_"; + kIfFunction += std::to_string(time_off._Raw); + + syntaxLeaf.fUserValue = "\tlda r12, extern_segment "; + syntaxLeaf.fUserValue += kIfFunction + + "\n\t#r12 = Code to jump on, r11 right cond, r10 left cond.\n\tbeq " + "r10, r11, r12\ndword public_segment .code64 " + + kIfFunction + "\n"; + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + kIfFound = true; + } + + // Parse expressions and instructions here. + // what does this mean? + // we encounter an assignment, or we reached the end of an expression. + if (text[text_index] == '=' || text[text_index] == ';') { + if (fnFound) continue; + if (kIfFound) continue; + + if (text[text_index] == ';' && kInStruct) continue; + + if (text.find("typedef ") != std::string::npos) continue; + + if (text[text_index] == '=' && kInStruct) { + Detail::print_error("assignement of value in struct " + text, file); + continue; + } + + if (text[text_index] == ';' && kInStruct) { + bool space_found_ = false; + std::string sym; + + for (auto& ch : text) { + if (ch == ' ') { + space_found_ = true; + } + + if (ch == ';') break; + + if (space_found_) sym.push_back(ch); + } + + kState.kStructMap[kState.kStructMap.size() - 1].fOffsets.push_back( + std::make_pair(kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt + 4, sym)); + + kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt = + kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt + 4; + + continue; + } + + if (text[text_index] == '=' && kInStruct) { + continue; + } + + if (text[text_index + 1] == '=' || text[text_index - 1] == '!' || + text[text_index - 1] == '<' || text[text_index - 1] == '>') { + continue; + } + + std::string substr; + + if (text.find('=') != std::string::npos && kInBraces && !kIfFound) { + if (text.find("*") != std::string::npos) { + if (text.find("=") > text.find("*")) + substr += "\tlda "; + else + substr += "\tldw "; + } else { + substr += "\tldw "; + } + } else if (text.find('=') != std::string::npos && !kInBraces) { + substr += "stw public_segment .data64 "; + } + + int first_encountered = 0; + + std::string str_name; + + for (size_t text_index_2 = 0; text_index_2 < text.size(); ++text_index_2) { + if (text[text_index_2] == '\"') { + ++text_index_2; + + // want to add this, so that the parser recognizes that this is a + // string. + substr += '"'; + + for (; text_index_2 < text.size(); ++text_index_2) { + if (text[text_index_2] == '\"') break; + + substr += text[text_index_2]; + } + } + + if (text[text_index_2] == '{' || text[text_index_2] == '}') continue; + + if (text[text_index_2] == ';') { + break; + } + + if (text[text_index_2] == ' ' || text[text_index_2] == '\t') { + if (first_encountered != 2) { + if (text[text_index] != '=' && + substr.find("public_segment .data64") == std::string::npos && !kInStruct) + substr += "public_segment .data64 "; + } + + ++first_encountered; + + continue; + } + + if (text[text_index_2] == '=') { + if (!kInBraces) { + substr.replace(substr.find("public_segment .data64"), strlen("public_segment .data64"), + "public_segment .zero64 "); + } + + substr += ","; + continue; + } + + substr += text[text_index_2]; + } + + for (auto& clType : kCompilerTypes) { + if (substr.find(clType.fName) != std::string::npos) { + if (substr.find(clType.fName) > substr.find('"')) continue; + + substr.erase(substr.find(clType.fName), clType.fName.size()); + } else if (substr.find(clType.fValue) != std::string::npos) { + if (substr.find(clType.fValue) > substr.find('"')) continue; + + if (clType.fName == "const") continue; + + substr.erase(substr.find(clType.fValue), clType.fValue.size()); + } + } + + if (substr.find("extern") != std::string::npos) { + substr.replace(substr.find("extern"), strlen("extern"), "extern_segment "); + + if (substr.find("public_segment .data64") != std::string::npos) + substr.erase(substr.find("public_segment .data64"), strlen("public_segment .data64")); + } + + auto var_to_find = std::find_if( + kCompilerVariables.cbegin(), kCompilerVariables.cend(), + [&](Detail::CompilerType type) { return type.fName.find(substr) != std::string::npos; }); + + if (kRegisterCounter == 5 || kRegisterCounter == 6) ++kRegisterCounter; + + std::string reg = kAsmRegisterPrefix; + reg += std::to_string(kRegisterCounter); + + if (var_to_find == kCompilerVariables.cend()) { + ++kRegisterCounter; + + kState.kStackFrame.push_back({.fName = substr, .fReg = reg}); + kCompilerVariables.push_back({.fName = substr}); + } + + syntaxLeaf.fUserValue += substr; + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + if (text[text_index] == '=') break; + } + + // function handler. + + if (text[text_index] == '(' && !fnFound && !kIfFound) { + std::string substr; + std::string args_buffer; + std::string args; + + bool type_crossed = false; + + for (size_t idx = text.find('(') + 1; idx < text.size(); ++idx) { + if (text[idx] == ',') continue; + + if (text[idx] == ' ') continue; + + if (text[idx] == ')') break; + } + + for (char substr_first_index : text) { + if (substr_first_index != ',') + args_buffer += substr_first_index; + else + args_buffer += '$'; + + if (substr_first_index == ';') { + args_buffer = args_buffer.erase(0, args_buffer.find('(')); + args_buffer = args_buffer.erase(args_buffer.find(';'), 1); + args_buffer = args_buffer.erase(args_buffer.find(')'), 1); + args_buffer = args_buffer.erase(args_buffer.find('('), 1); + + if (!args_buffer.empty()) args += "\tldw r6, "; + + std::string register_type; + std::size_t index = 7UL; + + while (args_buffer.find("$") != std::string::npos) { + register_type = kRegisterPrefix; + register_type += std::to_string(index); + + ++index; + + args_buffer.replace(args_buffer.find('$'), 1, "\n\tldw " + register_type + ","); + } + + args += args_buffer; + args += "\n\tlda r19, "; + } + } + + for (char _text_i : text) { + if (_text_i == '\t' || _text_i == ' ') { + if (!type_crossed) { + substr.clear(); + type_crossed = true; + } + + continue; + } + + if (_text_i == '(') break; + + substr += _text_i; + } + + if (kInBraces) { + syntaxLeaf.fUserValue = args; + syntaxLeaf.fUserValue += substr; + syntaxLeaf.fUserValue += "\n\tjrl\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + fnFound = true; + } else { + syntaxLeaf.fUserValue.clear(); + + syntaxLeaf.fUserValue += "public_segment .code64 "; + + syntaxLeaf.fUserValue += substr; + syntaxLeaf.fUserValue += "\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + fnFound = true; + } + + kCompilerFunctions.push_back(text); + } + + if (text[text_index] == '-' && text[text_index + 1] == '-') { + text = text.replace(text.find("--"), strlen("--"), ""); + + for (int _text_i = 0; _text_i < text.size(); ++_text_i) { + if (text[_text_i] == '\t' || text[_text_i] == ' ') text.erase(_text_i, 1); + } + + syntaxLeaf.fUserValue += "sub "; + syntaxLeaf.fUserValue += text; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + break; + } + + if (text[text_index] == '}') { + kRegisterCounter = kStartUsable; + + --kBracesCount; + + if (kBracesCount < 1) { + kInBraces = false; + kBracesCount = 0; + } + + if (kIfFound) kIfFound = false; + + if (kInStruct) kInStruct = false; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + } + + syntaxLeaf.fUserValue.clear(); + } + + auto syntaxLeaf = CompilerKit::SyntaxLeafList::SyntaxLeaf(); + syntaxLeaf.fUserValue = "\n"; + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + return syntaxLeaf; +} + +static bool kShouldHaveBraces = false; +static std::string kFnName; + +std::string CompilerFrontend64x0::Check(const char* text, const char* file) { + std::string err_str; + std::string ln = text; + + if (ln.empty()) { + return err_str; + } + + bool non_ascii_found = false; + + for (int i = 0; i < ln.size(); ++i) { + if (isalnum(ln[i])) { + non_ascii_found = true; + break; + } + } + + if (kShouldHaveBraces && ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + } + + if (!non_ascii_found) return err_str; + + size_t string_index = 1UL; + + if (ln.find('\'') != std::string::npos) { + string_index = ln.find('\'') + 1; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == '\'') { + if (ln[string_index + 1] != ';') { + ln.erase(string_index, 1); + } + + return err_str; + } + } + } else if (ln.find('"') != std::string::npos) { + string_index = ln.find('"') + 1; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == '"') { + if (ln[string_index + 1] != ';') { + ln.erase(string_index, 1); + } else { + break; + } + } + } + } else if (ln.find('"') == std::string::npos && ln.find('\'') == std::string::npos) { + std::vector forbidden_words; + + forbidden_words.push_back("\\"); + forbidden_words.push_back("?"); + forbidden_words.push_back("@"); + forbidden_words.push_back("~"); + forbidden_words.push_back("::"); + forbidden_words.push_back("--*"); + forbidden_words.push_back("*/"); + + // add them to avoid stupid mistakes. + forbidden_words.push_back("namespace"); + forbidden_words.push_back("class"); + forbidden_words.push_back("extern \"C\""); + + for (auto& forbidden : forbidden_words) { + if (ln.find(forbidden) != std::string::npos) { + err_str += "\nForbidden character detected: "; + err_str += forbidden; + + return err_str; + } + } + } + + struct CompilerVariableRange final { + std::string fBegin; + std::string fEnd; + }; + + const std::vector variables_list = { + {.fBegin = "static ", .fEnd = "="}, {.fBegin = "=", .fEnd = ";"}, + {.fBegin = "if(", .fEnd = "="}, {.fBegin = "if (", .fEnd = "="}, + {.fBegin = "if(", .fEnd = "<"}, {.fBegin = "if (", .fEnd = "<"}, + {.fBegin = "if(", .fEnd = ">"}, {.fBegin = "if (", .fEnd = ">"}, + {.fBegin = "if(", .fEnd = ")"}, {.fBegin = "if (", .fEnd = ")"}, + + {.fBegin = "else(", .fEnd = "="}, {.fBegin = "else (", .fEnd = "="}, + {.fBegin = "else(", .fEnd = "<"}, {.fBegin = "else (", .fEnd = "<"}, + {.fBegin = "else(", .fEnd = ">"}, {.fBegin = "else (", .fEnd = ">"}, + {.fBegin = "else(", .fEnd = ")"}, {.fBegin = "else (", .fEnd = ")"}, + }; + + for (auto& variable : variables_list) { + if (ln.find(variable.fBegin) != std::string::npos) { + string_index = ln.find(variable.fBegin) + variable.fBegin.size(); + + while (ln[string_index] == ' ') ++string_index; + + std::string keyword; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == variable.fEnd[0]) { + std::string varname = ""; + + for (size_t index_keyword = ln.find(' '); ln[index_keyword] != variable.fBegin[0]; + ++index_keyword) { + if (ln[index_keyword] == ' ') { + continue; + } + + if (isdigit(ln[index_keyword])) { + goto cc_next_loop; + } + + varname += ln[index_keyword]; + } + + if (varname.find(' ') != std::string::npos) { + varname.erase(0, varname.find(' ')); + + if (variable.fBegin == "extern") { + varname.erase(0, varname.find(' ')); + } + } + + if (kRegisterCounter == 5 || kRegisterCounter == 6) ++kRegisterCounter; + + std::string reg = kAsmRegisterPrefix; + reg += std::to_string(kRegisterCounter); + + kCompilerVariables.push_back({.fValue = varname}); + goto cc_check_done; + } + + keyword.push_back(ln[string_index]); + } + + goto cc_next_loop; + + cc_check_done: + + // skip digit value. + if (isdigit(keyword[0]) || keyword[0] == '"') { + goto cc_next_loop; + } + + while (keyword.find(' ') != std::string::npos) keyword.erase(keyword.find(' '), 1); + + for (auto& var : kCompilerVariables) { + if (var.fValue.find(keyword) != std::string::npos) { + err_str.clear(); + goto cc_next; + } + } + + for (auto& fn : kCompilerFunctions) { + if (fn.find(keyword[0]) != std::string::npos) { + auto where_begin = fn.find(keyword[0]); + auto keyword_begin = 0UL; + auto failed = false; + + for (; where_begin < keyword.size(); ++where_begin) { + if (fn[where_begin] == '(' && keyword[keyword_begin] == '(') break; + + if (fn[where_begin] != keyword[keyword_begin]) { + failed = true; + break; + } + + ++keyword_begin; + } + + if (!failed) { + err_str.clear(); + goto cc_next; + } else { + continue; + } + } + } + + cc_error_value: + if (keyword.find("->") != std::string::npos) return err_str; + + if (keyword.find(".") != std::string::npos) return err_str; + + if (isalnum(keyword[0])) err_str += "\nUndefined value: " + keyword; + + return err_str; + } + + cc_next_loop: + continue; + } + +cc_next: + + // extern does not declare anything, it extern_segments a variable. + // so that's why it's not declare upper. + if (CompilerKit::find_word(ln, "extern")) { + auto substr = ln.substr(ln.find("extern") + strlen("extern")); + kCompilerVariables.push_back({.fValue = substr}); + } + + if (kShouldHaveBraces && ln.find('{') == std::string::npos) { + err_str += "Missing '{' for function "; + err_str += kFnName; + err_str += "\n"; + + kShouldHaveBraces = false; + kFnName.clear(); + } else if (kShouldHaveBraces && ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + kFnName.clear(); + } + + bool type_not_found = true; + + if (ln.find('\'') != std::string::npos) { + ln.replace(ln.find('\''), 3, "0"); + } + + auto first = ln.find('"'); + if (first != std::string::npos) { + auto second = 0UL; + bool found_second_quote = false; + + for (size_t i = first + 1; i < ln.size(); ++i) { + if (ln[i] == '\"') { + found_second_quote = true; + second = i; + + break; + } + } + + if (!found_second_quote) { + err_str += "Missing terminating \"."; + err_str += " here -> " + ln.substr(ln.find('"'), second); + } + } + + if (ln.find(')') != std::string::npos && ln.find(';') == std::string::npos) { + if (ln.find('{') == std::string::npos) { + kFnName = ln; + kShouldHaveBraces = true; + + goto skip_braces_check; + } else if (ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + } + } + +skip_braces_check: + + for (auto& key : kCompilerTypes) { + if (CompilerKit::find_word(ln, key.fName)) { + if (isdigit(ln[ln.find(key.fName) + key.fName.size() + 1])) { + err_str += "\nNumber cannot be set for "; + err_str += key.fName; + err_str += "'s name. here -> "; + err_str += ln; + } + + if (ln.find(key.fName) == 0 || ln[ln.find(key.fName) - 1] == ' ' || + ln[ln.find(key.fName) - 1] == '\t') { + type_not_found = false; + + if (ln[ln.find(key.fName) + key.fName.size()] != ' ') { + type_not_found = true; + + if (ln[ln.find(key.fName) + key.fName.size()] == '\t') type_not_found = false; + + goto next; + } else if (ln[ln.find(key.fName) + key.fName.size()] != '\t') { + type_not_found = true; + + if (ln[ln.find(key.fName) + key.fName.size()] == ' ') type_not_found = false; + } + } + + next: + + if (ln.find(';') == std::string::npos) { + if (ln.find('(') != std::string::npos) { + if (ln.find('=') == std::string::npos) continue; + } + + err_str += "\nMissing ';', here -> "; + err_str += ln; + } else { + continue; + } + + if (ln.find('=') != std::string::npos) { + if (ln.find('(') != std::string::npos) { + if (ln.find(')') == std::string::npos) { + err_str += "\nMissing ')', after '(' here -> "; + err_str += ln.substr(ln.find('(')); + } + } + } + } + } + + if (kInBraces && ln.find("struct") != std::string::npos && + ln.find("union") != std::string::npos && ln.find("enum") != std::string::npos && + ln.find('=') != std::string::npos) { + if (ln.find(';') == std::string::npos) { + err_str += "\nMissing ';' after struct/union/enum declaration, here -> "; + err_str += ln; + } + } + + if (ln.find(';') != std::string::npos && ln.find("for") == std::string::npos) { + if (ln.find(';') + 1 != ln.size()) { + for (int i = 0; i < ln.substr(ln.find(';') + 1).size(); ++i) { + if ((ln.substr(ln.find(';') + 1)[i] != ' ') || (ln.substr(ln.find(';') + 1)[i] != '\t')) { + if (auto err = this->Check(ln.substr(ln.find(';') + 1).c_str(), file); !err.empty()) { + err_str += "\nUnexpected text after ';' -> "; + err_str += ln.substr(ln.find(';')); + err_str += err; + } + } + } + } + } + + if (ln.find('(') != std::string::npos) { + if (ln.find(';') == std::string::npos && !CompilerKit::find_word(ln, "|") && + !CompilerKit::find_word(ln, "||") && !CompilerKit::find_word(ln, "&") && + !CompilerKit::find_word(ln, "&&") && !CompilerKit::find_word(ln, "~")) { + bool found_func = false; + size_t i = ln.find('('); + std::vector opens; + std::vector closes; + + for (; i < ln.size(); ++i) { + if (ln[i] == ')') { + closes.push_back(1); + } + + if (ln[i] == '(') { + opens.push_back(1); + } + } + + if (closes.size() != opens.size()) err_str += "Unterminated (), here -> " + ln; + + bool space_found = false; + + for (int i = 0; i < ln.size(); ++i) { + if (ln[i] == ')' && !space_found) { + space_found = true; + continue; + } + + if (space_found) { + if (ln[i] == ' ' && isalnum(ln[i + 1])) { + err_str += "\nBad function format here -> "; + err_str += ln; + } + } + } + } + + if (ln.find('(') < 1) { + err_str += "\nMissing identifier before '(' here -> "; + err_str += ln; + } else { + if (type_not_found && ln.find(';') == std::string::npos && + ln.find("if") == std::string::npos && ln.find("|") == std::string::npos && + ln.find("&") == std::string::npos && ln.find("(") == std::string::npos && + ln.find(")") == std::string::npos) { + err_str += "\n Missing ';' or type, here -> "; + err_str += ln; + } + } + + if (ln.find(')') == std::string::npos) { + err_str += "\nMissing ')', after '(' here -> "; + err_str += ln.substr(ln.find('(')); + } + } else { + if (ln.find("for") != std::string::npos || ln.find("while") != std::string::npos) { + err_str += "\nMissing '(', after \"for\", here -> "; + err_str += ln; + } + } + + if (ln.find('}') != std::string::npos && !kInBraces) { + if (!kInStruct && ln.find(';') == std::string::npos) { + err_str += "\nMismatched '}', here -> "; + err_str += ln; + } + } + + if (!ln.empty()) { + if (ln.find(';') == std::string::npos && ln.find('{') == std::string::npos && + ln.find('}') == std::string::npos && ln.find(')') == std::string::npos && + ln.find('(') == std::string::npos && ln.find(',') == std::string::npos) { + if (ln.size() <= 2) return err_str; + + err_str += "\nMissing ';', here -> "; + err_str += ln; + } + } + + return err_str; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief C To Assembly mount-point. + */ + +///////////////////////////////////////////////////////////////////////////////////////// + +class AssemblyCCInterface final CK_ASSEMBLY_INTERFACE { + public: + explicit AssemblyCCInterface() = default; + ~AssemblyCCInterface() override = default; + + NECTI_COPY_DEFAULT(AssemblyCCInterface); + + UInt32 Arch() noexcept override { return CompilerKit::AssemblyFactory::kArch64x0; } + + Int32 CompileToFormat(std::string src, Int32 arch) override { + if (kCompilerFrontend == nullptr) return 1; + + /* @brief copy contents wihtout extension */ + std::string src_file = src.data(); + std::ifstream src_fp = std::ifstream(src_file, std::ios::in); + std::string dest; + + for (auto& ch : src_file) { + if (ch == '.') { + break; + } + + dest += ch; + } + + /* According to PEF ABI. */ + std::vector exts = kAsmFileExts; + dest += exts[4]; + + kState.fOutputAssembly = std::make_unique(dest); + + auto fmt = CompilerKit::current_date(); + + (*kState.fOutputAssembly) << "# Path: " << src_file << "\n"; + (*kState.fOutputAssembly) << "# Language: 64x0 Assembly (Generated from ANSI C)\n"; + (*kState.fOutputAssembly) << "# Date: " << fmt << "\n\n"; + + CompilerKit::SyntaxLeafList syntax; + + kState.fSyntaxTreeList.push_back(syntax); + kState.fSyntaxTree = &kState.fSyntaxTreeList[kState.fSyntaxTreeList.size() - 1]; + + std::string line_src; + + while (std::getline(src_fp, line_src)) { + if (auto err = kCompilerFrontend->Check(line_src.c_str(), src.data()); err.empty()) { + kCompilerFrontend->Compile(line_src, src.data()); + } else { + Detail::print_error(err, src.data()); + } + } + + if (kAcceptableErrors > 0) return 1; + + std::vector keywords = {"ldw", "stw", "lda", "sta", "add", "sub", "mv"}; + + /// + /// Replace, optimize, fix assembly output. + /// + + for (auto& leaf : kState.fSyntaxTree->fLeafList) { + std::vector access_keywords = {"->", "."}; + + for (auto& access_ident : access_keywords) { + if (CompilerKit::find_word(leaf.fUserValue, access_ident)) { + for (auto& struc : kState.kStructMap) { + /// TODO: + } + } + } + + for (auto& keyword : keywords) { + if (CompilerKit::find_word(leaf.fUserValue, keyword)) { + std::size_t cnt = 0UL; + + for (auto& reg : kState.kStackFrame) { + std::string needle; + + for (size_t i = 0; i < reg.fName.size(); i++) { + if (reg.fName[i] == ' ') { + ++i; + + for (; i < reg.fName.size(); i++) { + if (reg.fName[i] == ',') { + break; + } + + if (reg.fName[i] == ' ') continue; + + needle += reg.fName[i]; + } + + break; + } + } + + if (CompilerKit::find_word(leaf.fUserValue, needle)) { + if (leaf.fUserValue.find("extern_segment " + needle) != std::string::npos) { + std::string range = "extern_segment " + needle; + leaf.fUserValue.replace(leaf.fUserValue.find("extern_segment " + needle), + range.size(), needle); + } + + if (leaf.fUserValue.find("ldw r6") != std::string::npos) { + std::string::difference_type countComma = + std::count(leaf.fUserValue.begin(), leaf.fUserValue.end(), ','); + + if (countComma == 1) { + leaf.fUserValue.replace(leaf.fUserValue.find("ldw"), strlen("ldw"), "mv"); + } + } + + leaf.fUserValue.replace(leaf.fUserValue.find(needle), needle.size(), reg.fReg); + + ++cnt; + } + } + + if (cnt > 1 && keyword != "mv" && keyword != "add" && keyword != "sub") { + leaf.fUserValue.replace(leaf.fUserValue.find(keyword), keyword.size(), "mv"); + } + } + } + } + + for (auto& leaf : kState.fSyntaxTree->fLeafList) { + (*kState.fOutputAssembly) << leaf.fUserValue; + } + + kState.fSyntaxTree = nullptr; + + kState.fOutputAssembly->flush(); + kState.fOutputAssembly.reset(); + + return kExitOK; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +#include + +#define kPrintF printf +#define kSplashCxx() kPrintF(kWhite "NE C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) + +static void cc_print_help() { + kSplashCxx(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#define kExt ".c" + +NECTI_MODULE(CompilerCLang64x0) { + ::signal(SIGSEGV, Detail::drvi_crash_handler); + + kCompilerTypes.push_back({.fName = "void", .fValue = "void"}); + kCompilerTypes.push_back({.fName = "char", .fValue = "byte"}); + kCompilerTypes.push_back({.fName = "short", .fValue = "hword"}); + kCompilerTypes.push_back({.fName = "int", .fValue = "dword"}); + kCompilerTypes.push_back({.fName = "long", .fValue = "qword"}); + kCompilerTypes.push_back({.fName = "*", .fValue = "offset"}); + + bool skip = false; + + kFactory.Mount(new AssemblyCCInterface()); + kMachine = CompilerKit::AssemblyFactory::kArch64x0; + kCompilerFrontend = new CompilerFrontend64x0(); + + for (auto index = 1UL; index < argc; ++index) { + if (skip) { + skip = false; + continue; + } + + if (argv[index][0] == '-') { + if (strcmp(argv[index], "--v") == 0 || strcmp(argv[index], "--version") == 0) { + kSplashCxx(); + return kExitOK; + } + + if (strcmp(argv[index], "--verbose") == 0) { + kState.fVerbose = true; + + continue; + } + + if (strcmp(argv[index], "--h") == 0 || strcmp(argv[index], "--help") == 0) { + cc_print_help(); + + return kExitOK; + } + + if (strcmp(argv[index], "--dialect") == 0) { + if (kCompilerFrontend) std::cout << kCompilerFrontend->Language() << "\n"; + + return kExitOK; + } + + if (strcmp(argv[index], "--fmax-exceptions") == 0) { + try { + kErrorLimit = std::strtol(argv[index + 1], nullptr, 10); + } + // catch anything here + catch (...) { + kErrorLimit = 0; + } + + skip = true; + + continue; + } + + std::string err = "Unknown command: "; + err += argv[index]; + + Detail::print_error(err, "cc"); + + continue; + } + + kFileList.emplace_back(argv[index]); + + std::string srcFile = argv[index]; + + if (strstr(argv[index], kExt) == nullptr) { + if (kState.fVerbose) { + Detail::print_error(srcFile + " is not a valid C source.\n", "cc"); + } + + return 1; + } + + if (kFactory.Compile(srcFile, kMachine) != kExitOK) return 1; + } + + return kExitOK; +} + +// Last rev 8-1-24 diff --git a/src/CompilerKit/src/Frontend/CCompilerARM64.cc b/src/CompilerKit/src/Frontend/CCompilerARM64.cc new file mode 100644 index 0000000..c5b1fb8 --- /dev/null +++ b/src/CompilerKit/src/Frontend/CCompilerARM64.cc @@ -0,0 +1,1286 @@ +/* + * ======================================================== + * + * CCompilerARM64 + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +/// BUGS: 0 +/// TODO: none + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* C driver */ +/* This is part of the CompilerKit. */ +/* (c) Amlal El Mahrouss */ + +/// @author Amlal El Mahrouss (amlal@nekernel.org) +/// @file ARM64-cc.cc +/// @brief ARM64 C Compiler. + +/// TODO: support structures, else if, else, . and -> + +///////////////////// + +// ANSI ESCAPE CODES + +///////////////////// + +#define kExitOK (0) + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" + +///////////////////////////////////// + +// INTERNAL STUFF OF THE C COMPILER + +///////////////////////////////////// + +namespace Detail { +// \brief Register map structure, used to keep track of each variable's registers. +struct CompilerRegisterMap final { + std::string fName; + std::string fReg; +}; + +// \brief Map for C structs +// \author amlal@nekernel.org +struct CompilerStructMap final { + // 'my_foo' + std::string fName; + + // if instance: stores a valid register. + std::string fReg; + + // offset count + std::size_t fOffsetsCnt; + + // offset array. + std::vector> fOffsets; +}; + +struct CompilerState final { + std::vector fSyntaxTreeList; + std::vector kStackFrame; + std::vector kStructMap; + CompilerKit::SyntaxLeafList* fSyntaxTree{nullptr}; + std::unique_ptr fOutputAssembly; + std::string fLastFile; + std::string fLastError; + bool fVerbose; +}; +} // namespace Detail + +static Detail::CompilerState kState; +static std::string kIfFunction = ""; + +namespace Detail { +/// @brief prints an error into stdout. +/// @param reason the reason of the error. +/// @param file where does it originate from? +void print_error(std::string reason, std::string file) noexcept; + +struct CompilerType final { + std::string fName; + std::string fValue; +}; +} // namespace Detail + +///////////////////////////////////////////////////////////////////////////////////////// + +// Target architecture. +static int kMachine = 0; + +///////////////////////////////////////// + +// REGISTERS ACCORDING TO USED ASSEMBLER + +///////////////////////////////////////// + +static size_t kRegisterCnt = kAsmRegisterLimit; +static size_t kStartUsable = 8; +static size_t kUsableLimit = 15; +static size_t kRegisterCounter = kStartUsable; +static std::string kRegisterPrefix = kAsmRegisterPrefix; + +///////////////////////////////////////// + +// COMPILER PARSING UTILITIES/STATES. + +///////////////////////////////////////// + +static std::vector kFileList; +static CompilerKit::AssemblyFactory kFactory; +static bool kInStruct = false; +static bool kOnWhileLoop = false; +static bool kOnForLoop = false; +static bool kInBraces = false; +static bool kIfFound = false; +static size_t kBracesCount = 0UL; + +/* @brief C compiler backend for C */ +class CompilerFrontendARM64 final : public CompilerKit::CompilerFrontendInterface { + public: + explicit CompilerFrontendARM64() = default; + ~CompilerFrontendARM64() override = default; + + NECTI_COPY_DEFAULT(CompilerFrontendARM64); + + std::string Check(const char* text, const char* file); + CompilerKit::SyntaxLeafList::SyntaxLeaf Compile(std::string text, std::string file) override; + + const char* Language() override { return "64k C"; } +}; + +static CompilerFrontendARM64* kCompilerFrontend = nullptr; +static std::vector kCompilerVariables; +static std::vector kCompilerFunctions; +static std::vector kCompilerTypes; + +namespace Detail { +union number_cast final { + public: + number_cast(UInt64 _Raw) : _Raw(_Raw) {} + + public: + char _Num[8]; + UInt64 _Raw; +}; + +union double_cast final { + public: + double_cast(float _Raw) : _Raw(_Raw) {} + + public: + char _Sign; + char _Lh[8]; + char _Rh[23]; + + float _Raw; +}; +} // namespace Detail + +///////////////////////////////////////////////////////////////////////////////////////// + +// @name Compile +// @brief Generate MASM from a C assignement. + +///////////////////////////////////////////////////////////////////////////////////////// + +CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendARM64::Compile(std::string text, + std::string file) { + bool typeFound = false; + bool fnFound = false; + + // setup generator. + std::random_device rd; + + auto seed_data = std::array{}; + std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + // start parsing + for (size_t text_index = 0; text_index < text.size(); ++text_index) { + auto syntaxLeaf = CompilerKit::SyntaxLeafList::SyntaxLeaf(); + + auto gen = uuids::uuid_random_generator{generator}; + uuids::uuid out = gen(); + + Detail::number_cast time_off = (UInt64) out.as_bytes().data(); + + if (!typeFound) { + auto substr = text.substr(text_index); + std::string match_type; + + for (size_t y = 0; y < substr.size(); ++y) { + if (substr[y] == ' ') { + while (match_type.find(' ') != std::string::npos) { + match_type.erase(match_type.find(' ')); + } + + for (auto& clType : kCompilerTypes) { + if (clType.fName == match_type) { + match_type.clear(); + + std::string buf; + + buf += clType.fValue; + buf += ' '; + + if (substr.find('=') != std::string::npos) { + break; + } + + if (text.find('(') != std::string::npos) { + syntaxLeaf.fUserValue = buf; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + } + + typeFound = true; + break; + } + } + + break; + } + + match_type += substr[y]; + } + } + + if (text[text_index] == '{') { + if (kInStruct) { + continue; + } + + kInBraces = true; + ++kBracesCount; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + } + + // return keyword handler + if (text[text_index] == 'r') { + std::string return_keyword; + return_keyword += "return"; + + std::size_t index = 0UL; + + std::string value; + + for (size_t return_index = text_index; return_index < text.size(); ++return_index) { + if (text[return_index] != return_keyword[index]) { + for (size_t value_index = return_index; value_index < text.size(); ++value_index) { + if (text[value_index] == ';') break; + + value += text[value_index]; + } + + break; + } + + ++index; + } + + if (index == return_keyword.size()) { + if (!value.empty()) { + if (value.find('(') != std::string::npos) { + value.erase(value.find('(')); + } + + if (!isdigit(value[value.find('(') + 2])) { + std::string tmp = value; + bool reg_to_reg = false; + + value.clear(); + + value += " extern_segment"; + value += tmp; + } + + syntaxLeaf.fUserValue = "\tldw r19, "; + + // make it pretty. + if (value.find('\t') != std::string::npos) value.erase(value.find('\t'), 1); + + syntaxLeaf.fUserValue += value + "\n"; + } + + syntaxLeaf.fUserValue += "\tjlr"; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + break; + } + } + + if (text[text_index] == 'i' && text[text_index + 1] == 'f') { + auto expr = text.substr(text_index + 2); + text.erase(text_index, 2); + + if (expr.find("{") != std::string::npos) { + expr.erase(expr.find("{")); + } + + if (expr.find("(") != std::string::npos) expr.erase(expr.find("(")); + + if (expr.find(")") != std::string::npos) expr.erase(expr.find(")")); + + kIfFunction = "__NECTI_IF_PROC_"; + kIfFunction += std::to_string(time_off._Raw); + + syntaxLeaf.fUserValue = "\tlda r12, extern_segment "; + syntaxLeaf.fUserValue += kIfFunction + + "\n\t#r12 = Code to jump on, r11 right cond, r10 left cond.\n\tbeq " + "r10, r11, r12\ndword public_segment .code64 " + + kIfFunction + "\n"; + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + kIfFound = true; + } + + // Parse expressions and instructions here. + // what does this mean? + // we encounter an assignment, or we reached the end of an expression. + if (text[text_index] == '=' || text[text_index] == ';') { + if (fnFound) continue; + if (kIfFound) continue; + + if (text[text_index] == ';' && kInStruct) continue; + + if (text.find("typedef ") != std::string::npos) continue; + + if (text[text_index] == '=' && kInStruct) { + Detail::print_error("assignement of value in struct " + text, file); + continue; + } + + if (text[text_index] == ';' && kInStruct) { + bool space_found_ = false; + std::string sym; + + for (auto& ch : text) { + if (ch == ' ') { + space_found_ = true; + } + + if (ch == ';') break; + + if (space_found_) sym.push_back(ch); + } + + kState.kStructMap[kState.kStructMap.size() - 1].fOffsets.push_back( + std::make_pair(kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt + 4, sym)); + + kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt = + kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt + 4; + + continue; + } + + if (text[text_index] == '=' && kInStruct) { + continue; + } + + if (text[text_index + 1] == '=' || text[text_index - 1] == '!' || + text[text_index - 1] == '<' || text[text_index - 1] == '>') { + continue; + } + + std::string substr; + + if (text.find('=') != std::string::npos && kInBraces && !kIfFound) { + if (text.find("*") != std::string::npos) { + if (text.find("=") > text.find("*")) + substr += "\tlda "; + else + substr += "\tldw "; + } else { + substr += "\tldw "; + } + } else if (text.find('=') != std::string::npos && !kInBraces) { + substr += "stw public_segment .data64 "; + } + + int first_encountered = 0; + + std::string str_name; + + for (size_t text_index_2 = 0; text_index_2 < text.size(); ++text_index_2) { + if (text[text_index_2] == '\"') { + ++text_index_2; + + // want to add this, so that the parser recognizes that this is a + // string. + substr += '"'; + + for (; text_index_2 < text.size(); ++text_index_2) { + if (text[text_index_2] == '\"') break; + + substr += text[text_index_2]; + } + } + + if (text[text_index_2] == '{' || text[text_index_2] == '}') continue; + + if (text[text_index_2] == ';') { + break; + } + + if (text[text_index_2] == ' ' || text[text_index_2] == '\t') { + if (first_encountered != 2) { + if (text[text_index] != '=' && + substr.find("public_segment .data64") == std::string::npos && !kInStruct) + substr += "public_segment .data64 "; + } + + ++first_encountered; + + continue; + } + + if (text[text_index_2] == '=') { + if (!kInBraces) { + substr.replace(substr.find("public_segment .data64"), strlen("public_segment .data64"), + "public_segment .zero64 "); + } + + substr += ","; + continue; + } + + substr += text[text_index_2]; + } + + for (auto& clType : kCompilerTypes) { + if (substr.find(clType.fName) != std::string::npos) { + if (substr.find(clType.fName) > substr.find('"')) continue; + + substr.erase(substr.find(clType.fName), clType.fName.size()); + } else if (substr.find(clType.fValue) != std::string::npos) { + if (substr.find(clType.fValue) > substr.find('"')) continue; + + if (clType.fName == "const") continue; + + substr.erase(substr.find(clType.fValue), clType.fValue.size()); + } + } + + if (substr.find("extern") != std::string::npos) { + substr.replace(substr.find("extern"), strlen("extern"), "extern_segment "); + + if (substr.find("public_segment .data64") != std::string::npos) + substr.erase(substr.find("public_segment .data64"), strlen("public_segment .data64")); + } + + auto var_to_find = std::find_if( + kCompilerVariables.cbegin(), kCompilerVariables.cend(), + [&](Detail::CompilerType type) { return type.fName.find(substr) != std::string::npos; }); + + if (kRegisterCounter == 5 || kRegisterCounter == 6) ++kRegisterCounter; + + std::string reg = kAsmRegisterPrefix; + reg += std::to_string(kRegisterCounter); + + if (var_to_find == kCompilerVariables.cend()) { + ++kRegisterCounter; + + kState.kStackFrame.push_back({.fName = substr, .fReg = reg}); + kCompilerVariables.push_back({.fName = substr}); + } + + syntaxLeaf.fUserValue += substr; + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + if (text[text_index] == '=') break; + } + + // function handler. + + if (text[text_index] == '(' && !fnFound && !kIfFound) { + std::string substr; + std::string args_buffer; + std::string args; + + bool type_crossed = false; + + for (size_t idx = text.find('(') + 1; idx < text.size(); ++idx) { + if (text[idx] == ',') continue; + + if (text[idx] == ' ') continue; + + if (text[idx] == ')') break; + } + + for (char substr_first_index : text) { + if (substr_first_index != ',') + args_buffer += substr_first_index; + else + args_buffer += '$'; + + if (substr_first_index == ';') { + args_buffer = args_buffer.erase(0, args_buffer.find('(')); + args_buffer = args_buffer.erase(args_buffer.find(';'), 1); + args_buffer = args_buffer.erase(args_buffer.find(')'), 1); + args_buffer = args_buffer.erase(args_buffer.find('('), 1); + + if (!args_buffer.empty()) args += "\tldw r6, "; + + std::string register_type; + std::size_t index = 7UL; + + while (args_buffer.find("$") != std::string::npos) { + register_type = kRegisterPrefix; + register_type += std::to_string(index); + + ++index; + + args_buffer.replace(args_buffer.find('$'), 1, "\n\tldw " + register_type + ","); + } + + args += args_buffer; + args += "\n\tlda r19, "; + } + } + + for (char _text_i : text) { + if (_text_i == '\t' || _text_i == ' ') { + if (!type_crossed) { + substr.clear(); + type_crossed = true; + } + + continue; + } + + if (_text_i == '(') break; + + substr += _text_i; + } + + if (kInBraces) { + syntaxLeaf.fUserValue = args; + syntaxLeaf.fUserValue += substr; + syntaxLeaf.fUserValue += "\n\tjrl\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + fnFound = true; + } else { + syntaxLeaf.fUserValue.clear(); + + syntaxLeaf.fUserValue += "public_segment .code64 "; + + syntaxLeaf.fUserValue += substr; + syntaxLeaf.fUserValue += "\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + fnFound = true; + } + + kCompilerFunctions.push_back(text); + } + + if (text[text_index] == '-' && text[text_index + 1] == '-') { + text = text.replace(text.find("--"), strlen("--"), ""); + + for (int _text_i = 0; _text_i < text.size(); ++_text_i) { + if (text[_text_i] == '\t' || text[_text_i] == ' ') text.erase(_text_i, 1); + } + + syntaxLeaf.fUserValue += "sub "; + syntaxLeaf.fUserValue += text; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + break; + } + + if (text[text_index] == '}') { + kRegisterCounter = kStartUsable; + + --kBracesCount; + + if (kBracesCount < 1) { + kInBraces = false; + kBracesCount = 0; + } + + if (kIfFound) kIfFound = false; + + if (kInStruct) kInStruct = false; + + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + } + + syntaxLeaf.fUserValue.clear(); + } + + auto syntaxLeaf = CompilerKit::SyntaxLeafList::SyntaxLeaf(); + syntaxLeaf.fUserValue = "\n"; + kState.fSyntaxTree->fLeafList.push_back(syntaxLeaf); + + return syntaxLeaf; +} + +static bool kShouldHaveBraces = false; +static std::string kFnName; + +std::string CompilerFrontendARM64::Check(const char* text, const char* file) { + std::string err_str; + std::string ln = text; + + if (ln.empty()) { + return err_str; + } + + bool non_ascii_found = false; + + for (int i = 0; i < ln.size(); ++i) { + if (isalnum(ln[i])) { + non_ascii_found = true; + break; + } + } + + if (kShouldHaveBraces && ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + } + + if (!non_ascii_found) return err_str; + + size_t string_index = 1UL; + + if (ln.find('\'') != std::string::npos) { + string_index = ln.find('\'') + 1; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == '\'') { + if (ln[string_index + 1] != ';') { + ln.erase(string_index, 1); + } + + return err_str; + } + } + } else if (ln.find('"') != std::string::npos) { + string_index = ln.find('"') + 1; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == '"') { + if (ln[string_index + 1] != ';') { + ln.erase(string_index, 1); + } else { + break; + } + } + } + } else if (ln.find('"') == std::string::npos && ln.find('\'') == std::string::npos) { + std::vector forbidden_words; + + forbidden_words.push_back("\\"); + forbidden_words.push_back("?"); + forbidden_words.push_back("@"); + forbidden_words.push_back("~"); + forbidden_words.push_back("::"); + forbidden_words.push_back("--*"); + forbidden_words.push_back("*/"); + + // add them to avoid stupid mistakes. + forbidden_words.push_back("namespace"); + forbidden_words.push_back("class"); + forbidden_words.push_back("extern \"C\""); + + for (auto& forbidden : forbidden_words) { + if (ln.find(forbidden) != std::string::npos) { + err_str += "\nForbidden character detected: "; + err_str += forbidden; + + return err_str; + } + } + } + + struct CompilerVariableRange final { + std::string fBegin; + std::string fEnd; + }; + + const std::vector variables_list = { + {.fBegin = "static ", .fEnd = "="}, {.fBegin = "=", .fEnd = ";"}, + {.fBegin = "if(", .fEnd = "="}, {.fBegin = "if (", .fEnd = "="}, + {.fBegin = "if(", .fEnd = "<"}, {.fBegin = "if (", .fEnd = "<"}, + {.fBegin = "if(", .fEnd = ">"}, {.fBegin = "if (", .fEnd = ">"}, + {.fBegin = "if(", .fEnd = ")"}, {.fBegin = "if (", .fEnd = ")"}, + + {.fBegin = "else(", .fEnd = "="}, {.fBegin = "else (", .fEnd = "="}, + {.fBegin = "else(", .fEnd = "<"}, {.fBegin = "else (", .fEnd = "<"}, + {.fBegin = "else(", .fEnd = ">"}, {.fBegin = "else (", .fEnd = ">"}, + {.fBegin = "else(", .fEnd = ")"}, {.fBegin = "else (", .fEnd = ")"}, + }; + + for (auto& variable : variables_list) { + if (ln.find(variable.fBegin) != std::string::npos) { + string_index = ln.find(variable.fBegin) + variable.fBegin.size(); + + while (ln[string_index] == ' ') ++string_index; + + std::string keyword; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == variable.fEnd[0]) { + std::string varname = ""; + + for (size_t index_keyword = ln.find(' '); ln[index_keyword] != variable.fBegin[0]; + ++index_keyword) { + if (ln[index_keyword] == ' ') { + continue; + } + + if (isdigit(ln[index_keyword])) { + goto cc_next_loop; + } + + varname += ln[index_keyword]; + } + + if (varname.find(' ') != std::string::npos) { + varname.erase(0, varname.find(' ')); + + if (variable.fBegin == "extern") { + varname.erase(0, varname.find(' ')); + } + } + + if (kRegisterCounter == 5 || kRegisterCounter == 6) ++kRegisterCounter; + + std::string reg = kAsmRegisterPrefix; + reg += std::to_string(kRegisterCounter); + + kCompilerVariables.push_back({.fValue = varname}); + goto cc_check_done; + } + + keyword.push_back(ln[string_index]); + } + + goto cc_next_loop; + + cc_check_done: + + // skip digit value. + if (isdigit(keyword[0]) || keyword[0] == '"') { + goto cc_next_loop; + } + + while (keyword.find(' ') != std::string::npos) keyword.erase(keyword.find(' '), 1); + + for (auto& var : kCompilerVariables) { + if (var.fValue.find(keyword) != std::string::npos) { + err_str.clear(); + goto cc_next; + } + } + + for (auto& fn : kCompilerFunctions) { + if (fn.find(keyword[0]) != std::string::npos) { + auto where_begin = fn.find(keyword[0]); + auto keyword_begin = 0UL; + auto failed = false; + + for (; where_begin < keyword.size(); ++where_begin) { + if (fn[where_begin] == '(' && keyword[keyword_begin] == '(') break; + + if (fn[where_begin] != keyword[keyword_begin]) { + failed = true; + break; + } + + ++keyword_begin; + } + + if (!failed) { + err_str.clear(); + goto cc_next; + } else { + continue; + } + } + } + + cc_error_value: + if (keyword.find("->") != std::string::npos) return err_str; + + if (keyword.find(".") != std::string::npos) return err_str; + + if (isalnum(keyword[0])) err_str += "\nUndefined value: " + keyword; + + return err_str; + } + + cc_next_loop: + continue; + } + +cc_next: + + // extern does not declare anything, it extern_segments a variable. + // so that's why it's not declare upper. + if (CompilerKit::find_word(ln, "extern")) { + auto substr = ln.substr(ln.find("extern") + strlen("extern")); + kCompilerVariables.push_back({.fValue = substr}); + } + + if (kShouldHaveBraces && ln.find('{') == std::string::npos) { + err_str += "Missing '{' for function "; + err_str += kFnName; + err_str += "\n"; + + kShouldHaveBraces = false; + kFnName.clear(); + } else if (kShouldHaveBraces && ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + kFnName.clear(); + } + + bool type_not_found = true; + + if (ln.find('\'') != std::string::npos) { + ln.replace(ln.find('\''), 3, "0"); + } + + auto first = ln.find('"'); + if (first != std::string::npos) { + auto second = 0UL; + bool found_second_quote = false; + + for (size_t i = first + 1; i < ln.size(); ++i) { + if (ln[i] == '\"') { + found_second_quote = true; + second = i; + + break; + } + } + + if (!found_second_quote) { + err_str += "Missing terminating \"."; + err_str += " here -> " + ln.substr(ln.find('"'), second); + } + } + + if (ln.find(')') != std::string::npos && ln.find(';') == std::string::npos) { + if (ln.find('{') == std::string::npos) { + kFnName = ln; + kShouldHaveBraces = true; + + goto skip_braces_check; + } else if (ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + } + } + +skip_braces_check: + + for (auto& key : kCompilerTypes) { + if (CompilerKit::find_word(ln, key.fName)) { + if (isdigit(ln[ln.find(key.fName) + key.fName.size() + 1])) { + err_str += "\nNumber cannot be set for "; + err_str += key.fName; + err_str += "'s name. here -> "; + err_str += ln; + } + + if (ln.find(key.fName) == 0 || ln[ln.find(key.fName) - 1] == ' ' || + ln[ln.find(key.fName) - 1] == '\t') { + type_not_found = false; + + if (ln[ln.find(key.fName) + key.fName.size()] != ' ') { + type_not_found = true; + + if (ln[ln.find(key.fName) + key.fName.size()] == '\t') type_not_found = false; + + goto next; + } else if (ln[ln.find(key.fName) + key.fName.size()] != '\t') { + type_not_found = true; + + if (ln[ln.find(key.fName) + key.fName.size()] == ' ') type_not_found = false; + } + } + + next: + + if (ln.find(';') == std::string::npos) { + if (ln.find('(') != std::string::npos) { + if (ln.find('=') == std::string::npos) continue; + } + + err_str += "\nMissing ';', here -> "; + err_str += ln; + } else { + continue; + } + + if (ln.find('=') != std::string::npos) { + if (ln.find('(') != std::string::npos) { + if (ln.find(')') == std::string::npos) { + err_str += "\nMissing ')', after '(' here -> "; + err_str += ln.substr(ln.find('(')); + } + } + } + } + } + + if (kInBraces && ln.find("struct") != std::string::npos && + ln.find("union") != std::string::npos && ln.find("enum") != std::string::npos && + ln.find('=') != std::string::npos) { + if (ln.find(';') == std::string::npos) { + err_str += "\nMissing ';' after struct/union/enum declaration, here -> "; + err_str += ln; + } + } + + if (ln.find(';') != std::string::npos && ln.find("for") == std::string::npos) { + if (ln.find(';') + 1 != ln.size()) { + for (int i = 0; i < ln.substr(ln.find(';') + 1).size(); ++i) { + if ((ln.substr(ln.find(';') + 1)[i] != ' ') || (ln.substr(ln.find(';') + 1)[i] != '\t')) { + if (auto err = this->Check(ln.substr(ln.find(';') + 1).c_str(), file); !err.empty()) { + err_str += "\nUnexpected text after ';' -> "; + err_str += ln.substr(ln.find(';')); + err_str += err; + } + } + } + } + } + + if (ln.find('(') != std::string::npos) { + if (ln.find(';') == std::string::npos && !CompilerKit::find_word(ln, "|") && + !CompilerKit::find_word(ln, "||") && !CompilerKit::find_word(ln, "&") && + !CompilerKit::find_word(ln, "&&") && !CompilerKit::find_word(ln, "~")) { + bool found_func = false; + size_t i = ln.find('('); + std::vector opens; + std::vector closes; + + for (; i < ln.size(); ++i) { + if (ln[i] == ')') { + closes.push_back(1); + } + + if (ln[i] == '(') { + opens.push_back(1); + } + } + + if (closes.size() != opens.size()) err_str += "Unterminated (), here -> " + ln; + + bool space_found = false; + + for (int i = 0; i < ln.size(); ++i) { + if (ln[i] == ')' && !space_found) { + space_found = true; + continue; + } + + if (space_found) { + if (ln[i] == ' ' && isalnum(ln[i + 1])) { + err_str += "\nBad function format here -> "; + err_str += ln; + } + } + } + } + + if (ln.find('(') < 1) { + err_str += "\nMissing identifier before '(' here -> "; + err_str += ln; + } else { + if (type_not_found && ln.find(';') == std::string::npos && + ln.find("if") == std::string::npos && ln.find("|") == std::string::npos && + ln.find("&") == std::string::npos && ln.find("(") == std::string::npos && + ln.find(")") == std::string::npos) { + err_str += "\n Missing ';' or type, here -> "; + err_str += ln; + } + } + + if (ln.find(')') == std::string::npos) { + err_str += "\nMissing ')', after '(' here -> "; + err_str += ln.substr(ln.find('(')); + } + } else { + if (ln.find("for") != std::string::npos || ln.find("while") != std::string::npos) { + err_str += "\nMissing '(', after \"for\", here -> "; + err_str += ln; + } + } + + if (ln.find('}') != std::string::npos && !kInBraces) { + if (!kInStruct && ln.find(';') == std::string::npos) { + err_str += "\nMismatched '}', here -> "; + err_str += ln; + } + } + + if (!ln.empty()) { + if (ln.find(';') == std::string::npos && ln.find('{') == std::string::npos && + ln.find('}') == std::string::npos && ln.find(')') == std::string::npos && + ln.find('(') == std::string::npos && ln.find(',') == std::string::npos) { + if (ln.size() <= 2) return err_str; + + err_str += "\nMissing ';', here -> "; + err_str += ln; + } + } + + return err_str; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief C To Assembly mount-point. + */ + +///////////////////////////////////////////////////////////////////////////////////////// + +class AssemblyCCInterface final CK_ASSEMBLY_INTERFACE { + public: + explicit AssemblyCCInterface() = default; + ~AssemblyCCInterface() override = default; + + NECTI_COPY_DEFAULT(AssemblyCCInterface); + + UInt32 Arch() noexcept override { return CompilerKit::AssemblyFactory::kArchAARCH64; } + + Int32 CompileToFormat(std::string src, Int32 arch) override { + if (kCompilerFrontend == nullptr) return 1; + + /* @brief copy contents wihtout extension */ + std::string src_file = src.data(); + std::ifstream src_fp = std::ifstream(src_file, std::ios::in); + std::string dest; + + for (auto& ch : src_file) { + if (ch == '.') { + break; + } + + dest += ch; + } + + /* According to PEF ABI. */ + std::vector exts = kAsmFileExts; + dest += exts[4]; + + kState.fOutputAssembly = std::make_unique(dest); + + auto fmt = CompilerKit::current_date(); + + (*kState.fOutputAssembly) << "# Path: " << src_file << "\n"; + (*kState.fOutputAssembly) << "# Language: ARM64 Assembly (Generated from ANSI C)\n"; + (*kState.fOutputAssembly) << "# Date: " << fmt << "\n\n"; + + CompilerKit::SyntaxLeafList syntax; + + kState.fSyntaxTreeList.push_back(syntax); + kState.fSyntaxTree = &kState.fSyntaxTreeList[kState.fSyntaxTreeList.size() - 1]; + + std::string line_src; + + while (std::getline(src_fp, line_src)) { + if (auto err = kCompilerFrontend->Check(line_src.c_str(), src.data()); err.empty()) { + kCompilerFrontend->Compile(line_src, src.data()); + } else { + Detail::print_error(err, src.data()); + } + } + + if (kAcceptableErrors > 0) return 1; + + std::vector keywords = {"ldw", "stw", "lda", "sta", "add", "sub", "mv"}; + + /// + /// Replace, optimize, fix assembly output. + /// + + for (auto& leaf : kState.fSyntaxTree->fLeafList) { + std::vector access_keywords = {"->", "."}; + + for (auto& access_ident : access_keywords) { + if (CompilerKit::find_word(leaf.fUserValue, access_ident)) { + for (auto& struc : kState.kStructMap) { + /// TODO: + } + } + } + + for (auto& keyword : keywords) { + if (CompilerKit::find_word(leaf.fUserValue, keyword)) { + std::size_t cnt = 0UL; + + for (auto& reg : kState.kStackFrame) { + std::string needle; + + for (size_t i = 0; i < reg.fName.size(); i++) { + if (reg.fName[i] == ' ') { + ++i; + + for (; i < reg.fName.size(); i++) { + if (reg.fName[i] == ',') { + break; + } + + if (reg.fName[i] == ' ') continue; + + needle += reg.fName[i]; + } + + break; + } + } + + if (CompilerKit::find_word(leaf.fUserValue, needle)) { + if (leaf.fUserValue.find("extern_segment " + needle) != std::string::npos) { + std::string range = "extern_segment " + needle; + leaf.fUserValue.replace(leaf.fUserValue.find("extern_segment " + needle), + range.size(), needle); + } + + if (leaf.fUserValue.find("ldw r6") != std::string::npos) { + std::string::difference_type countComma = + std::count(leaf.fUserValue.begin(), leaf.fUserValue.end(), ','); + + if (countComma == 1) { + leaf.fUserValue.replace(leaf.fUserValue.find("ldw"), strlen("ldw"), "mv"); + } + } + + leaf.fUserValue.replace(leaf.fUserValue.find(needle), needle.size(), reg.fReg); + + ++cnt; + } + } + + if (cnt > 1 && keyword != "mv" && keyword != "add" && keyword != "sub") { + leaf.fUserValue.replace(leaf.fUserValue.find(keyword), keyword.size(), "mv"); + } + } + } + } + + for (auto& leaf : kState.fSyntaxTree->fLeafList) { + (*kState.fOutputAssembly) << leaf.fUserValue; + } + + kState.fSyntaxTree = nullptr; + + kState.fOutputAssembly->flush(); + kState.fOutputAssembly.reset(); + + return kExitOK; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +#include + +#define kPrintF printf +#define kSplashCxx() kPrintF(kWhite "NE C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) + +static void cc_print_help() { + kSplashCxx(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#define kCExtension ".c" + +NECTI_MODULE(CompilerCLangARM64) { + ::signal(SIGSEGV, Detail::drvi_crash_handler); + + kCompilerTypes.push_back({.fName = "void", .fValue = "void"}); + kCompilerTypes.push_back({.fName = "char", .fValue = "byte"}); + kCompilerTypes.push_back({.fName = "short", .fValue = "hword"}); + kCompilerTypes.push_back({.fName = "int", .fValue = "dword"}); + kCompilerTypes.push_back({.fName = "long", .fValue = "qword"}); + kCompilerTypes.push_back({.fName = "*", .fValue = "offset"}); + + bool skip = false; + + kFactory.Mount(new AssemblyCCInterface()); + kMachine = CompilerKit::AssemblyFactory::kArchAARCH64; + kCompilerFrontend = new CompilerFrontendARM64(); + + for (auto index = 1UL; index < argc; ++index) { + if (skip) { + skip = false; + continue; + } + + if (argv[index][0] == '-') { + if (strcmp(argv[index], "--v") == 0 || strcmp(argv[index], "--version") == 0) { + kSplashCxx(); + return kExitOK; + } + + if (strcmp(argv[index], "--verbose") == 0) { + kState.fVerbose = true; + + continue; + } + + if (strcmp(argv[index], "--h") == 0 || strcmp(argv[index], "--help") == 0) { + cc_print_help(); + + return kExitOK; + } + + if (strcmp(argv[index], "--dialect") == 0) { + if (kCompilerFrontend) std::cout << kCompilerFrontend->Language() << "\n"; + + return kExitOK; + } + + if (strcmp(argv[index], "--fmax-exceptions") == 0) { + try { + kErrorLimit = std::strtol(argv[index + 1], nullptr, 10); + } + // catch anything here + catch (...) { + kErrorLimit = 0; + } + + skip = true; + + continue; + } + + std::string err = "Unknown command: "; + err += argv[index]; + + Detail::print_error(err, "cc"); + + continue; + } + + kFileList.emplace_back(argv[index]); + + std::string srcFile = argv[index]; + + if (strstr(argv[index], kCExtension) == nullptr) { + if (kState.fVerbose) { + Detail::print_error(srcFile + " is not a valid C source.\n", "cc"); + } + + return 1; + } + + if (kFactory.Compile(srcFile, kMachine) != kExitOK) return 1; + } + + return kExitOK; +} + +// Last rev 8-1-24 diff --git a/src/CompilerKit/src/Frontend/CCompilerPower64.cc b/src/CompilerKit/src/Frontend/CCompilerPower64.cc new file mode 100644 index 0000000..ccc5cf8 --- /dev/null +++ b/src/CompilerKit/src/Frontend/CCompilerPower64.cc @@ -0,0 +1,1304 @@ +/* + * ======================================================== + * + * CompilerPower64 + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define kExitOK 0 + +/// @author Amlal El Mahrouss (amlal@nekernel.org) +/// @file cc.cc +/// @brief POWER64 C Compiler. + +///////////////////// + +/// ANSI ESCAPE CODES + +///////////////////// + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" + +///////////////////////////////////// + +/// INTERNAL STRUCTURES OF THE C COMPILER + +///////////////////////////////////// + +namespace Detail { +// \brief name to register struct. +struct CompilerRegisterMap final { + std::string fName; + std::string fReg; +}; + +// \brief Map for C structs +// \author amlal@nekernel.org +struct CompilerStructMap final { + /// 'struct::my_foo' + std::string fName; + + /// if instance: stores a valid register. + std::string fReg; + + /// offset count + std::size_t fOffsetsCnt; + + /// offset array. + std::vector> fOffsets; +}; + +struct CompilerState final { + std::vector fSyntaxTreeList; + std::vector kStackFrame; + std::vector kStructMap; + CompilerKit::SyntaxLeafList* fSyntaxTree{nullptr}; + std::unique_ptr fOutputAssembly; + std::string fLastFile; + std::string fLastError; + bool fVerbose; +}; +} // namespace Detail + +static Detail::CompilerState kState; +static std::string kIfFunction = ""; + +namespace Detail { +/// @brief prints an error into stdout. +/// @param reason the reason of the error. +/// @param file where does it originate from? +void print_error(std::string reason, std::string file) noexcept; + +struct CompilerType final { + std::string fName; + std::string fValue; +}; +} // namespace Detail + +///////////////////////////////////////////////////////////////////////////////////////// + +// Target architecture. +static int kMachine = 0; + +///////////////////////////////////////// + +// REGISTERS ACCORDING TO USED ASSEMBLER + +///////////////////////////////////////// + +static size_t kRegisterCnt = kAsmRegisterLimit; +static size_t kStartUsable = 2; +static size_t kUsableLimit = 15; +static size_t kRegisterCounter = kStartUsable; +static std::string kRegisterPrefix = kAsmRegisterPrefix; + +///////////////////////////////////////// + +// COMPILER PARSING UTILITIES/STATES. + +///////////////////////////////////////// + +static std::vector kFileList; +static CompilerKit::AssemblyFactory kFactory; +static bool kInStruct = false; +static bool kOnWhileLoop = false; +static bool kOnForLoop = false; +static bool kInBraces = false; +static bool kIfFound = false; +static size_t kBracesCount = 0UL; + +/* @brief C compiler backend for C */ +class CompilerFrontendPower64 final : public CompilerKit::CompilerFrontendInterface { + public: + explicit CompilerFrontendPower64() = default; + ~CompilerFrontendPower64() override = default; + + NECTI_COPY_DEFAULT(CompilerFrontendPower64); + + std::string Check(const char* text, const char* file); + CompilerKit::SyntaxLeafList::SyntaxLeaf Compile(std::string text, std::string file) override; + + const char* Language() override { return "POWER C"; } +}; + +static CompilerFrontendPower64* kCompilerFrontend = nullptr; +static std::vector kCompilerVariables; +static std::vector kCompilerFunctions; +static std::vector kCompilerTypes; + +namespace Detail { +union number_cast final { + public: + number_cast(UInt64 _Raw) : _Raw(_Raw) {} + + public: + char _Num[8]; + UInt64 _Raw; +}; + +union double_cast final { + public: + double_cast(float _Raw) : _Raw(_Raw) {} + + public: + char _Sign; + char _Lh[8]; + char _Rh[23]; + + float _Raw; +}; +} // namespace Detail + +///////////////////////////////////////////////////////////////////////////////////////// + +// @name Compile +// @brief Generate MASM from a C assignement. + +///////////////////////////////////////////////////////////////////////////////////////// + +CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendPower64::Compile(std::string text_, + std::string file) { + std::string text = text_; + + bool typeFound = false; + bool fnFound = false; + + // setup generator. + std::random_device rd; + + auto seed_data = std::array{}; + std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + // start parsing + for (size_t text_index = 0; text_index < text.size(); ++text_index) { + auto syntax_leaf = CompilerKit::SyntaxLeafList::SyntaxLeaf(); + + auto gen = uuids::uuid_random_generator{generator}; + uuids::uuid out = gen(); + + Detail::number_cast time_off = (UInt64) out.as_bytes().data(); + + if (!typeFound) { + auto substr = text.substr(text_index); + std::string match_type; + + for (size_t y = 0; y < substr.size(); ++y) { + if (substr[y] == ' ') { + while (match_type.find(' ') != std::string::npos) { + match_type.erase(match_type.find(' ')); + } + + for (auto& clType : kCompilerTypes) { + if (clType.fName == match_type) { + match_type.clear(); + + std::string buf; + + buf += clType.fValue; + buf += ' '; + + if (substr.find('=') != std::string::npos) { + break; + } + + if (text.find('(') != std::string::npos) { + syntax_leaf.fUserValue = buf; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + } + + typeFound = true; + break; + } + } + + break; + } + + match_type += substr[y]; + } + } + + if (text[text_index] == '{') { + if (kInStruct) { + continue; + } + + kInBraces = true; + ++kBracesCount; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + } + + // return keyword handler + if (text[text_index] == 'r') { + std::string return_keyword; + return_keyword += "return"; + + std::size_t index = 0UL; + + std::string value; + + for (size_t return_index = text_index; return_index < text.size(); ++return_index) { + if (text[return_index] != return_keyword[index]) { + for (size_t value_index = return_index; value_index < text.size(); ++value_index) { + if (text[value_index] == ';') break; + + value += text[value_index]; + } + + break; + } + + ++index; + } + + if (index == return_keyword.size()) { + if (!value.empty()) { + if (value.find('(') != std::string::npos) { + value.erase(value.find('(')); + } + + if (!isdigit(value[value.find('(') + 2])) { + std::string tmp = value; + bool reg_to_reg = false; + + value.clear(); + + value += " extern_segment"; + value += tmp; + } + + syntax_leaf.fUserValue = "\tmr r31, "; + + // make it pretty. + while (value.find('\t') != std::string::npos) value.erase(value.find('\t'), 1); + + while (value.find(' ') != std::string::npos) value.erase(value.find(' '), 1); + + while (value.find("extern_segment") != std::string::npos) + value.erase(value.find("extern_segment"), strlen("extern_segment")); + + bool found = false; + + for (auto& reg : kState.kStackFrame) { + if (value.find(reg.fName) != std::string::npos) { + found = true; + syntax_leaf.fUserValue += reg.fReg; + break; + } + } + + if (!found) syntax_leaf.fUserValue += "r0"; + } + + syntax_leaf.fUserValue += "\n\tblr"; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + + break; + } + } + + if (text[text_index] == 'i' && text[text_index + 1] == 'f') { + auto expr = text.substr(text_index + 2); + text.erase(text_index, 2); + + if (expr.find("{") != std::string::npos) { + expr.erase(expr.find("{")); + } + + if (expr.find("(") != std::string::npos) expr.erase(expr.find("(")); + + if (expr.find(")") != std::string::npos) expr.erase(expr.find(")")); + + kIfFunction = "__NECTI_IF_PROC_"; + kIfFunction += std::to_string(time_off._Raw); + + syntax_leaf.fUserValue = + "\tcmpw " + "r10, r11"; + + syntax_leaf.fUserValue += "\n\tbeq extern_segment " + kIfFunction + + " \ndword public_segment .code64 " + kIfFunction + "\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + + kIfFound = true; + } + + // Parse expressions and instructions here. + // what does this mean? + // we encounter an assignment, or we reached the end of an expression. + if (text[text_index] == '=' || text[text_index] == ';') { + if (fnFound) continue; + if (kIfFound) continue; + + if (text[text_index] == ';' && kInStruct) continue; + + if (text.find("typedef ") != std::string::npos) continue; + + if (text[text_index] == '=' && kInStruct) { + Detail::print_error("assignement of value inside a struct " + text, file); + continue; + } + + if (text[text_index] == ';' && kInStruct) { + bool space_found_ = false; + std::string sym; + + for (auto& ch : text) { + if (ch == ' ') { + space_found_ = true; + } + + if (ch == ';') break; + + if (space_found_) sym.push_back(ch); + } + + kState.kStructMap[kState.kStructMap.size() - 1].fOffsets.push_back( + std::make_pair(kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt + 4, sym)); + + kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt = + kState.kStructMap[kState.kStructMap.size() - 1].fOffsetsCnt + 4; + + continue; + } + + if (text[text_index] == '=' && kInStruct) { + continue; + } + + if (text[text_index + 1] == '=' || text[text_index - 1] == '!' || + text[text_index - 1] == '<' || text[text_index - 1] == '>') { + continue; + } + + std::string substr; + + if (text.find('=') != std::string::npos && kInBraces && !kIfFound) { + if (text.find("*") != std::string::npos) { + if (text.find("=") > text.find("*")) + substr += "\tli "; + else + substr += "\tli "; + } else { + substr += "\tli "; + } + } else if (text.find('=') != std::string::npos && !kInBraces) { + substr += "stw public_segment .data64 "; + } + + int first_encountered = 0; + + std::string str_name; + + for (size_t text_index_2 = 0; text_index_2 < text.size(); ++text_index_2) { + if (text[text_index_2] == '\"') { + ++text_index_2; + + // want to add this, so that the parser recognizes that this is a + // string. + substr += '"'; + + for (; text_index_2 < text.size(); ++text_index_2) { + if (text[text_index_2] == '\"') break; + + substr += text[text_index_2]; + } + } + + if (text[text_index_2] == '{' || text[text_index_2] == '}') continue; + + if (text[text_index_2] == ';') { + break; + } + + if (text[text_index_2] == ' ' || text[text_index_2] == '\t') { + if (first_encountered != 2) { + if (text[text_index] != '=' && + substr.find("public_segment .data64") == std::string::npos && !kInStruct) + substr += "public_segment .data64 "; + } + + ++first_encountered; + + continue; + } + + if (text[text_index_2] == '=') { + if (!kInBraces) { + substr.replace(substr.find("public_segment .data64"), strlen("public_segment .data64"), + "public_segment .zero64 "); + } + + substr += ","; + continue; + } + + substr += text[text_index_2]; + } + + for (auto& clType : kCompilerTypes) { + if (substr.find(clType.fName) != std::string::npos) { + if (substr.find(clType.fName) > substr.find('"')) continue; + + substr.erase(substr.find(clType.fName), clType.fName.size()); + } else if (substr.find(clType.fValue) != std::string::npos) { + if (substr.find(clType.fValue) > substr.find('"')) continue; + + if (clType.fName == "const") continue; + + substr.erase(substr.find(clType.fValue), clType.fValue.size()); + } + } + + if (substr.find("extern") != std::string::npos) { + substr.replace(substr.find("extern"), strlen("extern"), "extern_segment "); + + if (substr.find("public_segment .data64") != std::string::npos) + substr.erase(substr.find("public_segment .data64"), strlen("public_segment .data64")); + } + + auto var_to_find = std::find_if( + kCompilerVariables.cbegin(), kCompilerVariables.cend(), + [&](Detail::CompilerType type) { return type.fName.find(substr) != std::string::npos; }); + + kCompilerVariables.push_back({.fName = substr}); + + if (text[text_index] == ';') break; + + std::string reg = kAsmRegisterPrefix; + + ++kRegisterCounter; + reg += std::to_string(kRegisterCounter); + + auto newSubstr = substr.substr(substr.find(" ")); + + std::string symbol; + + for (size_t start = 0; start < newSubstr.size(); ++start) { + if (newSubstr[start] == ',') break; + + if (newSubstr[start] == ' ') continue; + + symbol += (newSubstr[start]); + } + + kState.kStackFrame.push_back({.fName = symbol, .fReg = reg}); + + syntax_leaf.fUserValue += "\n\tli " + reg + substr.substr(substr.find(',')); + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + } + + // function handler. + + if (text[text_index] == '(' && !fnFound && !kIfFound) { + std::string substr; + std::string args_buffer; + std::string args; + + bool type_crossed = false; + + for (size_t idx = text.find('(') + 1; idx < text.size(); ++idx) { + if (text[idx] == ',') continue; + + if (text[idx] == ' ') continue; + + if (text[idx] == ')') break; + } + + for (char substr_first_index : text) { + if (substr_first_index != ',') + args_buffer += substr_first_index; + else + args_buffer += '$'; + + if (substr_first_index == ';') { + args_buffer = args_buffer.erase(0, args_buffer.find('(')); + args_buffer = args_buffer.erase(args_buffer.find(';'), 1); + args_buffer = args_buffer.erase(args_buffer.find(')'), 1); + args_buffer = args_buffer.erase(args_buffer.find('('), 1); + + if (!args_buffer.empty()) args += "\tldw r6, "; + + std::string register_type; + std::size_t index = 7UL; + + while (args_buffer.find("$") != std::string::npos) { + register_type = kRegisterPrefix; + register_type += std::to_string(index); + + ++index; + + args_buffer.replace(args_buffer.find('$'), 1, "\n\tldw " + register_type + ","); + } + + args += args_buffer; + args += "\n\tli r31, "; + } + } + + for (char _text_i : text) { + if (_text_i == '\t' || _text_i == ' ') { + if (!type_crossed) { + substr.clear(); + type_crossed = true; + } + + continue; + } + + if (_text_i == '(') break; + + substr += _text_i; + } + + if (kInBraces) { + syntax_leaf.fUserValue = args; + syntax_leaf.fUserValue += substr; + + syntax_leaf.fUserValue += "\n\tblr\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + + fnFound = true; + } else { + syntax_leaf.fUserValue.clear(); + + syntax_leaf.fUserValue += "public_segment .code64 "; + + syntax_leaf.fUserValue += substr; + syntax_leaf.fUserValue += "\n"; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + + fnFound = true; + } + + kCompilerFunctions.push_back(text); + } + + if (text[text_index] == '-' && text[text_index + 1] == '-') { + text = text.replace(text.find("--"), strlen("--"), ""); + + for (int _text_i = 0; _text_i < text.size(); ++_text_i) { + if (text[_text_i] == '\t' || text[_text_i] == ' ') text.erase(_text_i, 1); + } + + syntax_leaf.fUserValue += "dec "; + syntax_leaf.fUserValue += text; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + break; + } + + if (text[text_index] == '}') { + kRegisterCounter = kStartUsable; + + --kBracesCount; + + if (kBracesCount < 1) { + kInBraces = false; + kBracesCount = 0; + } + + if (kIfFound) kIfFound = false; + + if (kInStruct) kInStruct = false; + + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + } + + syntax_leaf.fUserValue.clear(); + } + + auto syntax_leaf = CompilerKit::SyntaxLeafList::SyntaxLeaf(); + syntax_leaf.fUserValue = "\n"; + kState.fSyntaxTree->fLeafList.push_back(syntax_leaf); + + return syntax_leaf; +} + +static bool kShouldHaveBraces = false; +static std::string kFnName; + +std::string CompilerFrontendPower64::Check(const char* text, const char* file) { + std::string err_str; + std::string ln = text; + + if (ln.empty()) { + return err_str; + } + + bool non_ascii_found = false; + + for (int i = 0; i < ln.size(); ++i) { + if (isalnum(ln[i])) { + non_ascii_found = true; + break; + } + } + + if (kShouldHaveBraces && ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + } + + if (!non_ascii_found) return err_str; + + size_t string_index = 1UL; + + if (ln.find('\'') != std::string::npos) { + string_index = ln.find('\'') + 1; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == '\'') { + if (ln[string_index + 1] != ';') { + ln.erase(string_index, 1); + } + + return err_str; + } + } + } else if (ln.find('"') != std::string::npos) { + string_index = ln.find('"') + 1; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == '"') { + if (ln[string_index + 1] != ';') { + ln.erase(string_index, 1); + } else { + break; + } + } + } + } else if (ln.find('"') == std::string::npos && ln.find('\'') == std::string::npos) { + std::vector forbidden_words; + + forbidden_words.push_back("\\"); + forbidden_words.push_back("?"); + forbidden_words.push_back("@"); + forbidden_words.push_back("~"); + forbidden_words.push_back("::"); + forbidden_words.push_back("--*"); + forbidden_words.push_back("*/"); + + // add them to avoid stupid mistakes. + forbidden_words.push_back("namespace"); + forbidden_words.push_back("class"); + forbidden_words.push_back("extern \"C\""); + + for (auto& forbidden : forbidden_words) { + if (ln.find(forbidden) != std::string::npos) { + err_str += "\nForbidden character detected: "; + err_str += forbidden; + + return err_str; + } + } + } + + struct CompilerVariableRange final { + std::string fBegin; + std::string fEnd; + }; + + const std::vector variables_list = { + {.fBegin = "static ", .fEnd = "="}, {.fBegin = "=", .fEnd = ";"}, + {.fBegin = "if(", .fEnd = "="}, {.fBegin = "if (", .fEnd = "="}, + {.fBegin = "if(", .fEnd = "<"}, {.fBegin = "if (", .fEnd = "<"}, + {.fBegin = "if(", .fEnd = ">"}, {.fBegin = "if (", .fEnd = ">"}, + {.fBegin = "if(", .fEnd = ")"}, {.fBegin = "if (", .fEnd = ")"}, + + {.fBegin = "else(", .fEnd = "="}, {.fBegin = "else (", .fEnd = "="}, + {.fBegin = "else(", .fEnd = "<"}, {.fBegin = "else (", .fEnd = "<"}, + {.fBegin = "else(", .fEnd = ">"}, {.fBegin = "else (", .fEnd = ">"}, + {.fBegin = "else(", .fEnd = ")"}, {.fBegin = "else (", .fEnd = ")"}, + }; + + for (auto& variable : variables_list) { + if (ln.find(variable.fBegin) != std::string::npos) { + string_index = ln.find(variable.fBegin) + variable.fBegin.size(); + + while (ln[string_index] == ' ') ++string_index; + + std::string keyword; + + for (; string_index < ln.size(); ++string_index) { + if (ln[string_index] == variable.fEnd[0]) { + std::string varname = ""; + + for (size_t index_keyword = ln.find(' '); ln[index_keyword] != variable.fBegin[0]; + ++index_keyword) { + if (ln[index_keyword] == ' ') { + continue; + } + + if (isdigit(ln[index_keyword])) { + goto cc_next_loop; + } + + varname += ln[index_keyword]; + } + + if (varname.find(' ') != std::string::npos) { + varname.erase(0, varname.find(' ')); + + if (variable.fBegin == "extern") { + varname.erase(0, varname.find(' ')); + } + } + + if (kRegisterCounter == 5 || kRegisterCounter == 6) ++kRegisterCounter; + + std::string reg = kAsmRegisterPrefix; + reg += std::to_string(kRegisterCounter); + + kCompilerVariables.push_back({.fValue = varname}); + goto cc_check_done; + } + + keyword.push_back(ln[string_index]); + } + + goto cc_next_loop; + + cc_check_done: + + // skip digit value. + if (isdigit(keyword[0]) || keyword[0] == '"') { + goto cc_next_loop; + } + + while (keyword.find(' ') != std::string::npos) keyword.erase(keyword.find(' '), 1); + + for (auto& var : kCompilerVariables) { + if (var.fValue.find(keyword) != std::string::npos) { + err_str.clear(); + goto cc_next; + } + } + + for (auto& fn : kCompilerFunctions) { + if (fn.find(keyword[0]) != std::string::npos) { + auto where_begin = fn.find(keyword[0]); + auto keyword_begin = 0UL; + auto failed = false; + + for (; where_begin < keyword.size(); ++where_begin) { + if (fn[where_begin] == '(' && keyword[keyword_begin] == '(') break; + + if (fn[where_begin] != keyword[keyword_begin]) { + failed = true; + break; + } + + ++keyword_begin; + } + + if (!failed) { + err_str.clear(); + goto cc_next; + } else { + continue; + } + } + } + + cc_error_value: + if (keyword.find("->") != std::string::npos) return err_str; + + if (keyword.find(".") != std::string::npos) return err_str; + + if (isalnum(keyword[0])) err_str += "\nUndefined value: " + keyword; + + return err_str; + } + + cc_next_loop: + continue; + } + +cc_next: + + // extern does not declare anything, it extern_segments a variable. + // so that's why it's not declare upper. + if (CompilerKit::find_word(ln, "extern")) { + auto substr = ln.substr(ln.find("extern") + strlen("extern")); + kCompilerVariables.push_back({.fValue = substr}); + } + + if (kShouldHaveBraces && ln.find('{') == std::string::npos) { + err_str += "Missing '{' for function "; + err_str += kFnName; + err_str += "\n"; + + kShouldHaveBraces = false; + kFnName.clear(); + } else if (kShouldHaveBraces && ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + kFnName.clear(); + } + + bool type_not_found = true; + + if (ln.find('\'') != std::string::npos) { + ln.replace(ln.find('\''), 3, "0"); + } + + auto first = ln.find('"'); + if (first != std::string::npos) { + auto second = 0UL; + bool found_second_quote = false; + + for (size_t i = first + 1; i < ln.size(); ++i) { + if (ln[i] == '\"') { + found_second_quote = true; + second = i; + + break; + } + } + + if (!found_second_quote) { + err_str += "Missing terminating \"."; + err_str += " here -> " + ln.substr(ln.find('"'), second); + } + } + + if (ln.find(')') != std::string::npos && ln.find(';') == std::string::npos) { + if (ln.find('{') == std::string::npos) { + kFnName = ln; + kShouldHaveBraces = true; + + goto skip_braces_check; + } else if (ln.find('{') != std::string::npos) { + kShouldHaveBraces = false; + } + } + +skip_braces_check: + + for (auto& key : kCompilerTypes) { + if (CompilerKit::find_word(ln, key.fName)) { + if (isdigit(ln[ln.find(key.fName) + key.fName.size() + 1])) { + err_str += "\nNumber cannot be set for "; + err_str += key.fName; + err_str += "'s name. here -> "; + err_str += ln; + } + + if (ln.find(key.fName) == 0 || ln[ln.find(key.fName) - 1] == ' ' || + ln[ln.find(key.fName) - 1] == '\t') { + type_not_found = false; + + if (ln[ln.find(key.fName) + key.fName.size()] != ' ') { + type_not_found = true; + + if (ln[ln.find(key.fName) + key.fName.size()] == '\t') type_not_found = false; + + goto next; + } else if (ln[ln.find(key.fName) + key.fName.size()] != '\t') { + type_not_found = true; + + if (ln[ln.find(key.fName) + key.fName.size()] == ' ') type_not_found = false; + } + } + + next: + + if (ln.find(';') == std::string::npos) { + if (ln.find('(') != std::string::npos) { + if (ln.find('=') == std::string::npos) continue; + } + + err_str += "\nMissing ';', here -> "; + err_str += ln; + } else { + continue; + } + + if (ln.find('=') != std::string::npos) { + if (ln.find('(') != std::string::npos) { + if (ln.find(')') == std::string::npos) { + err_str += "\nMissing ')', after '(' here -> "; + err_str += ln.substr(ln.find('(')); + } + } + } + } + } + + if (kInBraces && ln.find("struct") != std::string::npos && + ln.find("union") != std::string::npos && ln.find("enum") != std::string::npos && + ln.find('=') != std::string::npos) { + if (ln.find(';') == std::string::npos) { + err_str += "\nMissing ';' after struct/union/enum declaration, here -> "; + err_str += ln; + } + } + + if (ln.find(';') != std::string::npos && ln.find("for") == std::string::npos) { + if (ln.find(';') + 1 != ln.size()) { + for (int i = 0; i < ln.substr(ln.find(';') + 1).size(); ++i) { + if ((ln.substr(ln.find(';') + 1)[i] != ' ') || (ln.substr(ln.find(';') + 1)[i] != '\t')) { + if (auto err = this->Check(ln.substr(ln.find(';') + 1).c_str(), file); !err.empty()) { + err_str += "\nUnexpected text after ';' -> "; + err_str += ln.substr(ln.find(';')); + err_str += err; + } + } + } + } + } + + if (ln.find('(') != std::string::npos) { + if (ln.find(';') == std::string::npos && !CompilerKit::find_word(ln, "|") && + !CompilerKit::find_word(ln, "||") && !CompilerKit::find_word(ln, "&") && + !CompilerKit::find_word(ln, "&&") && !CompilerKit::find_word(ln, "~")) { + bool found_func = false; + size_t i = ln.find('('); + std::vector opens; + std::vector closes; + + for (; i < ln.size(); ++i) { + if (ln[i] == ')') { + closes.push_back(1); + } + + if (ln[i] == '(') { + opens.push_back(1); + } + } + + if (closes.size() != opens.size()) err_str += "Unterminated (), here -> " + ln; + + bool space_found = false; + + for (int i = 0; i < ln.size(); ++i) { + if (ln[i] == ')' && !space_found) { + space_found = true; + continue; + } + + if (space_found) { + if (ln[i] == ' ' && isalnum(ln[i + 1])) { + err_str += "\nBad function format here -> "; + err_str += ln; + } + } + } + } + + if (ln.find('(') < 1) { + err_str += "\nMissing identifier before '(' here -> "; + err_str += ln; + } else { + if (type_not_found && ln.find(';') == std::string::npos && + ln.find("if") == std::string::npos && ln.find("|") == std::string::npos && + ln.find("&") == std::string::npos && ln.find("(") == std::string::npos && + ln.find(")") == std::string::npos) { + err_str += "\n Missing ';' or type, here -> "; + err_str += ln; + } + } + + if (ln.find(')') == std::string::npos) { + err_str += "\nMissing ')', after '(' here -> "; + err_str += ln.substr(ln.find('(')); + } + } else { + if (ln.find("for") != std::string::npos || ln.find("while") != std::string::npos) { + err_str += "\nMissing '(', after \"for\", here -> "; + err_str += ln; + } + } + + if (ln.find('}') != std::string::npos && !kInBraces) { + if (!kInStruct && ln.find(';') == std::string::npos) { + err_str += "\nMismatched '}', here -> "; + err_str += ln; + } + } + + if (!ln.empty()) { + if (ln.find(';') == std::string::npos && ln.find('{') == std::string::npos && + ln.find('}') == std::string::npos && ln.find(')') == std::string::npos && + ln.find('(') == std::string::npos && ln.find(',') == std::string::npos) { + if (ln.size() <= 2) return err_str; + + err_str += "\nMissing ';', here -> "; + err_str += ln; + } + } + + return err_str; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief C To Assembly mount-point. + */ + +///////////////////////////////////////////////////////////////////////////////////////// + +class AssemblyMountpointCLang final CK_ASSEMBLY_INTERFACE { + public: + explicit AssemblyMountpointCLang() = default; + ~AssemblyMountpointCLang() override = default; + + NECTI_COPY_DEFAULT(AssemblyMountpointCLang); + + UInt32 Arch() noexcept override { return CompilerKit::AssemblyFactory::kArchPowerPC; } + + Int32 CompileToFormat(std::string src, Int32 arch) override { + if (kCompilerFrontend == nullptr) return 1; + + /* @brief copy contents wihtout extension */ + std::string src_file = src.data(); + std::ifstream src_fp = std::ifstream(src_file, std::ios::in); + std::string dest; + + for (auto& ch : src_file) { + if (ch == '.') { + break; + } + + dest += ch; + } + + /* According to PEF ABI. */ + std::vector exts = kAsmFileExts; + dest += exts[4]; + + kState.fOutputAssembly = std::make_unique(dest); + + auto fmt = CompilerKit::current_date(); + + (*kState.fOutputAssembly) << "# Path: " << src_file << "\n"; + (*kState.fOutputAssembly) << "# Language: POWER Assembly (Generated from C)\n"; + (*kState.fOutputAssembly) << "# Date: " << fmt << "\n\n"; + + CompilerKit::SyntaxLeafList syntax; + + kState.fSyntaxTreeList.push_back(syntax); + kState.fSyntaxTree = &kState.fSyntaxTreeList[kState.fSyntaxTreeList.size() - 1]; + + std::string line_src; + + while (std::getline(src_fp, line_src)) { + if (auto err = kCompilerFrontend->Check(line_src.c_str(), src.data()); err.empty()) { + kCompilerFrontend->Compile(line_src, src.data()); + } else { + Detail::print_error(err, src.data()); + } + } + + if (kAcceptableErrors > 0) return 1; + + std::vector keywords = {"ld", "stw", "add", "sub", "or"}; + + /// + /// Replace, optimize, fix assembly output. + /// + + for (auto& leaf : kState.fSyntaxTree->fLeafList) { + std::vector access_keywords = {"->", "."}; + + for (auto& access_ident : access_keywords) { + if (CompilerKit::find_word(leaf.fUserValue, access_ident)) { + for (auto& struc : kState.kStructMap) { + /// TODO: + } + } + } + + for (auto& keyword : keywords) { + if (CompilerKit::find_word(leaf.fUserValue, keyword)) { + std::size_t cnt = 0UL; + + for (auto& reg : kState.kStackFrame) { + std::string needle; + + for (size_t i = 0; i < reg.fName.size(); i++) { + if (reg.fName[i] == ' ') { + ++i; + + for (; i < reg.fName.size(); i++) { + if (reg.fName[i] == ',') { + break; + } + + if (reg.fName[i] == ' ') continue; + + needle += reg.fName[i]; + } + + break; + } + } + + if (CompilerKit::find_word(leaf.fUserValue, needle)) { + if (leaf.fUserValue.find("extern_segment ") != std::string::npos) { + std::string range = "extern_segment "; + leaf.fUserValue.replace(leaf.fUserValue.find(range), range.size(), ""); + } + + if (leaf.fUserValue.find("ldw r6") != std::string::npos) { + std::string::difference_type countComma = + std::count(leaf.fUserValue.begin(), leaf.fUserValue.end(), ','); + + if (countComma == 1) { + leaf.fUserValue.replace(leaf.fUserValue.find("ldw"), strlen("ldw"), "mr"); + } + } + + leaf.fUserValue.replace(leaf.fUserValue.find(needle), needle.size(), reg.fReg); + + ++cnt; + } + } + + if (cnt > 1 && keyword != "mr" && keyword != "add" && keyword != "dec") { + leaf.fUserValue.replace(leaf.fUserValue.find(keyword), keyword.size(), "mr"); + } + } + } + } + + for (auto& leaf : kState.fSyntaxTree->fLeafList) { + (*kState.fOutputAssembly) << leaf.fUserValue; + } + + kState.fSyntaxTree = nullptr; + + kState.fOutputAssembly->flush(); + kState.fOutputAssembly.reset(); + + return kExitOK; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +#include + +#define kPrintF printf +#define kSplashCxx() kPrintF(kWhite "cc, %s, (c) Amlal El Mahrouss\n", kDistVersion) + +static void cc_print_help() { + kSplashCxx(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#define kExt ".c" + +NECTI_MODULE(CompilerCLangPowerPC) { + ::signal(SIGSEGV, Detail::drvi_crash_handler); + + kCompilerTypes.push_back({.fName = "void", .fValue = "void"}); + kCompilerTypes.push_back({.fName = "char", .fValue = "byte"}); + kCompilerTypes.push_back({.fName = "short", .fValue = "hword"}); + kCompilerTypes.push_back({.fName = "int", .fValue = "dword"}); + kCompilerTypes.push_back({.fName = "long", .fValue = "qword"}); + kCompilerTypes.push_back({.fName = "*", .fValue = "offset"}); + + bool skip = false; + + kFactory.Mount(new AssemblyMountpointCLang()); + kMachine = CompilerKit::AssemblyFactory::kArchPowerPC; + kCompilerFrontend = new CompilerFrontendPower64(); + + for (auto index = 1UL; index < argc; ++index) { + if (skip) { + skip = false; + continue; + } + + if (argv[index][0] == '-') { + if (strcmp(argv[index], "-v") == 0 || strcmp(argv[index], "-version") == 0) { + kSplashCxx(); + return kExitOK; + } + + if (strcmp(argv[index], "-verbose") == 0) { + kState.fVerbose = true; + + continue; + } + + if (strcmp(argv[index], "-h") == 0 || strcmp(argv[index], "-help") == 0) { + cc_print_help(); + + return kExitOK; + } + + if (strcmp(argv[index], "-dialect") == 0) { + if (kCompilerFrontend) std::cout << kCompilerFrontend->Language() << "\n"; + + return kExitOK; + } + + if (strcmp(argv[index], "-fmax-exceptions") == 0) { + try { + kErrorLimit = std::strtol(argv[index + 1], nullptr, 10); + } + // catch anything here + catch (...) { + kErrorLimit = 0; + } + + skip = true; + + continue; + } + + std::string err = "Unknown command: "; + err += argv[index]; + + Detail::print_error(err, "cc"); + + continue; + } + + kFileList.emplace_back(argv[index]); + + std::string srcFile = argv[index]; + + if (strstr(argv[index], kExt) == nullptr) { + if (kState.fVerbose) { + Detail::print_error(srcFile + " is not a valid C source.\n", "cc"); + } + + return 1; + } + + if (kFactory.Compile(srcFile, kMachine) != kExitOK) return 1; + } + + return kExitOK; +} + +// Last rev 8-1-24 diff --git a/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc b/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc new file mode 100644 index 0000000..d715a3a --- /dev/null +++ b/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc @@ -0,0 +1,906 @@ +/* + * ======================================================== + * + * C++ Compiler Driver + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +/// BUGS: 0 + +/////////////////////// + +// ANSI ESCAPE CODES // + +/////////////////////// + +/////////////////////// + +// MACROS // + +/////////////////////// + +#define kPrintF printf +#define kPrintErr std::cerr + +#define kExitOK (EXIT_SUCCESS) +#define kExitNO (EXIT_FAILURE) + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" + +#include +#include +#include +#include +#include +#include +#include + +/* NeKernel C++ Compiler Driver */ +/* This is part of the CompilerKit. */ +/* (c) Amlal El Mahrouss 2024-2025 */ + +/// @author Amlal El Mahrouss (amlal@nekernel.org) +/// @file CPlusPlusCompilerAMD64.cxx +/// @brief Optimized C++ Compiler Driver. + +///////////////////////////////////// + +// INTERNALS OF THE C++ COMPILER + +///////////////////////////////////// + +/// @internal +// Avoids relative_path which could discard parts of the original. +std::filesystem::path necti_expand_home(const std::filesystem::path& input) { + const std::string& raw = input.string(); + + if (!raw.empty() && raw[0] == '~') { + const char* home = std::getenv("HOME"); + if (!home) home = std::getenv("USERPROFILE"); + + if (!home) throw std::runtime_error("Home directory not found in environment variables"); + + return std::filesystem::path(home) / raw.substr(1); + } + + return input; +} + +struct CompilerRegisterMap final { + CompilerKit::STLString fName{}; + CompilerKit::STLString fReg{}; +}; + +/// \brief Offset based struct/class +struct CompilerStructMap final { + CompilerKit::STLString fName{}; + CompilerKit::STLString fReg{}; + std::vector> fOffsets; +}; + +/// \brief Compiler state structure. +struct CompilerState final { + std::vector fStackMapVector; + std::vector fStructMapVector; + CompilerKit::STLString fLastFile{}; + CompilerKit::STLString fLastError{}; +}; + +static CompilerState kState; + +static Int32 kOnClassScope = 0; + +///////////////////////////////////////////////////////////////////////////////////////// + +// Target architecture. +static Int32 kMachine = CompilerKit::AssemblyFactory::kArchAMD64; + +///////////////////////////////////////// + +// ARGUMENTS REGISTERS (R8, R15) + +///////////////////////////////////////// + +static std::vector kKeywords; + +///////////////////////////////////////// + +// COMPILER PARSING UTILITIES/STATES. + +///////////////////////////////////////// + +static CompilerKit::AssemblyFactory kAssembler; +static Boolean kInStruct = false; +static Boolean kOnWhileLoop = false; +static Boolean kOnForLoop = false; +static Boolean kInBraces = false; +static size_t kBracesCount = 0UL; + +/* @brief C++ compiler backend for the NeKernel C++ driver */ +class CompilerFrontendCPlusPlusAMD64 final CK_COMPILER_FRONTEND { + public: + explicit CompilerFrontendCPlusPlusAMD64() = default; + ~CompilerFrontendCPlusPlusAMD64() override = default; + + NECTI_COPY_DEFAULT(CompilerFrontendCPlusPlusAMD64); + + CompilerKit::SyntaxLeafList::SyntaxLeaf Compile(const CompilerKit::STLString text, + CompilerKit::STLString file) override; + + const char* Language() override; +}; + +/// @internal compiler variables + +static CompilerFrontendCPlusPlusAMD64* kFrontend = nullptr; + +static std::vector kRegisterMap; + +static std::vector kRegisterList = { + "rbx", "rsi", "r10", "r11", "r12", "r13", "r14", "r15", "xmm12", "xmm13", "xmm14", "xmm15", +}; + +/// @brief The PEF calling convention (caller must save rax, rbp) +/// @note callee must return via **rax**. +static std::vector kRegisterConventionCallList = { + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", +}; + +static std::size_t kFunctionEmbedLevel = 0UL; + +/// detail namespaces + +const char* CompilerFrontendCPlusPlusAMD64::Language() { + return "AMD64 C++"; +} + +static std::uintptr_t kOrigin = kPefBaseOrigin; +static std::vector> kOriginMap; + +///////////////////////////////////////////////////////////////////////////////////////// + +/// @name Compile +/// @brief Generate assembly from a C++ source. + +///////////////////////////////////////////////////////////////////////////////////////// + +CompilerKit::SyntaxLeafList::SyntaxLeaf CompilerFrontendCPlusPlusAMD64::Compile( + CompilerKit::STLString text, CompilerKit::STLString file) { + CompilerKit::SyntaxLeafList::SyntaxLeaf syntax_tree; + + if (text.empty()) return syntax_tree; + + std::size_t index = 0UL; + std::vector> keywords_list; + + for (auto& keyword : kKeywords) { + if (text.find(keyword.keyword_name) != std::string::npos) { + switch (keyword.keyword_kind) { + case CompilerKit::kKeywordKindCommentInline: { + break; + } + default: + break; + } + + std::size_t pos = text.find(keyword.keyword_name); + if (pos == std::string::npos) continue; + + // can't go before start of string + if (pos > 0 && text[pos - 1] == '+' && + keyword.keyword_kind == CompilerKit::kKeywordKindVariableAssign) + continue; + + if (pos > 0 && text[pos - 1] == '-' && + keyword.keyword_kind == CompilerKit::kKeywordKindVariableAssign) + continue; + + // don't go out of range + if ((pos + keyword.keyword_name.size()) < text.size() && + text[pos + keyword.keyword_name.size()] == '=' && + keyword.keyword_kind == CompilerKit::kKeywordKindVariableAssign) + continue; + + keywords_list.emplace_back(std::make_pair(keyword, index)); + ++index; + } + } + + for (auto& keyword : keywords_list) { + if (text.find(keyword.first.keyword_name) == CompilerKit::STLString::npos) continue; + + switch (keyword.first.keyword_kind) { + case CompilerKit::KeywordKind::kKeywordKindClass: { + ++kOnClassScope; + break; + } + case CompilerKit::KeywordKind::kKeywordKindIf: { + std::size_t keywordPos = text.find(keyword.first.keyword_name); + std::size_t openParen = text.find("(", keywordPos); + std::size_t closeParen = text.find(")", openParen); + + if (keywordPos == CompilerKit::STLString::npos || + openParen == CompilerKit::STLString::npos || + closeParen == CompilerKit::STLString::npos || closeParen <= openParen) { + Detail::print_error("Malformed if expression: " + text, file); + break; + } + + auto expr = text.substr(openParen + 1, closeParen - openParen - 1); + + if (expr.find(">=") != CompilerKit::STLString::npos) { + auto left = text.substr( + text.find(keyword.first.keyword_name) + keyword.first.keyword_name.size() + 2, + expr.find("<=") + strlen("<=")); + auto right = text.substr(expr.find(">=") + strlen(">="), text.find(")") - 1); + + // Trim whitespace + while (!right.empty() && (right.back() == ' ' || right.back() == '\t')) { + right.pop_back(); + } + while (!right.empty() && (right.front() == ' ' || right.front() == '\t')) { + right.erase(0, 1); + } + + while (!left.empty() && (left.back() == ' ' || left.back() == '\t')) { + left.pop_back(); + } + while (!left.empty() && (left.front() == ' ' || left.front() == '\t')) { + left.erase(0, 1); + } + + if ((!left.empty() && !isdigit(left[0])) || (!right.empty() && !isdigit(right[0]))) { + auto indexRight = 0UL; + + auto& valueOfVar = (!left.empty() && !isdigit(left[0])) ? left : right; + + if (!valueOfVar.empty()) { + for (auto pairRight : kRegisterMap) { + ++indexRight; + + CompilerKit::STLString instr = "mov "; + + if (pairRight != valueOfVar) { + auto& valueOfVarOpposite = (!left.empty() && isdigit(left[0])) ? left : right; + + syntax_tree.fUserValue += + instr + kRegisterList[indexRight + 1] + ", " + valueOfVarOpposite + "\n"; + syntax_tree.fUserValue += "cmp " + kRegisterList[kRegisterMap.size() - 1] + "," + + kRegisterList[indexRight + 1] + "\n"; + + goto lc_done_iterarting_on_if; + } + + auto& valueOfVarOpposite = (!left.empty() && isdigit(left[0])) ? left : right; + + syntax_tree.fUserValue += + instr + kRegisterList[indexRight + 1] + ", " + valueOfVarOpposite + "\n"; + syntax_tree.fUserValue += "cmp " + kRegisterList[kRegisterMap.size() - 1] + ", " + + kRegisterList[indexRight + 1] + "\n"; + + break; + } + } + } + + lc_done_iterarting_on_if: + + CompilerKit::STLString symbol_name_fn = text; + + symbol_name_fn.erase(symbol_name_fn.find(keyword.first.keyword_name)); + + for (auto& ch : symbol_name_fn) { + if (ch == ' ') ch = '_'; + } + + syntax_tree.fUserValue += + "jge __OFFSET_ON_TRUE_LC\nsegment .code64 __OFFSET_ON_TRUE_LC:\n"; + } + + break; + } + case CompilerKit::KeywordKind::kKeywordKindFunctionStart: { + for (auto& ch : text) { + if (isdigit(ch)) { + goto dont_accept; + } + } + + goto accept; + + dont_accept: + break; + + accept: + CompilerKit::STLString symbol_name_fn = text; + size_t indexFnName = 0; + + // this one is for the type. + for (auto& ch : text) { + ++indexFnName; + + if (ch == '\t') break; + if (ch == ' ') break; + } + + symbol_name_fn = text.substr(indexFnName); + + if (text.find("return ") != CompilerKit::STLString::npos) { + text.erase(0, text.find("return ")); + break; + } + + if (text.ends_with(";") && text.find("return") == CompilerKit::STLString::npos) + goto lc_write_assembly; + else if (text.size() <= indexFnName) + Detail::print_error("Invalid function name: " + symbol_name_fn, file); + + indexFnName = 0; + + for (auto& ch : symbol_name_fn) { + if (ch == ' ' || ch == '\t') { + if (symbol_name_fn[indexFnName - 1] != ')') + Detail::print_error("Invalid function name: " + symbol_name_fn, file); + } + + ++indexFnName; + } + + if (symbol_name_fn.find("(") != CompilerKit::STLString::npos) { + symbol_name_fn.erase(symbol_name_fn.find("(")); + } + + syntax_tree.fUserValue = "public_segment .code64 __NECTI_" + symbol_name_fn + "\n"; + ++kFunctionEmbedLevel; + + kOriginMap.push_back({"__NECTI_" + symbol_name_fn, kOrigin}); + + break; + + lc_write_assembly: + auto it = std::find_if( + kOriginMap.begin(), kOriginMap.end(), + [&symbol_name_fn](std::pair pair) -> bool { + return symbol_name_fn == pair.first; + }); + + if (it != kOriginMap.end()) { + std::stringstream ss; + ss << std::hex << it->second; + + syntax_tree.fUserValue = "jmp " + ss.str() + "\n"; + kOrigin += 1UL; + } + } + case CompilerKit::KeywordKind::kKeywordKindFunctionEnd: { + if (kOnClassScope) --kOnClassScope; + + if (text.ends_with(";")) break; + + --kFunctionEmbedLevel; + + if (kRegisterMap.size() > kRegisterList.size()) { + --kFunctionEmbedLevel; + } + + if (kFunctionEmbedLevel < 1) kRegisterMap.clear(); + + break; + } + case CompilerKit::KeywordKind::kKeywordKindEndInstr: + case CompilerKit::KeywordKind::kKeywordKindVariableInc: + case CompilerKit::KeywordKind::kKeywordKindVariableDec: + case CompilerKit::KeywordKind::kKeywordKindVariableAssign: { + CompilerKit::STLString valueOfVar = ""; + + if (keyword.first.keyword_kind == CompilerKit::KeywordKind::kKeywordKindVariableInc) { + valueOfVar = text.substr(text.find("+=") + 2); + } else if (keyword.first.keyword_kind == + CompilerKit::KeywordKind::kKeywordKindVariableDec) { + valueOfVar = text.substr(text.find("-=") + 2); + } else if (keyword.first.keyword_kind == + CompilerKit::KeywordKind::kKeywordKindVariableAssign) { + valueOfVar = text.substr(text.find("=") + 1); + } else if (keyword.first.keyword_kind == CompilerKit::KeywordKind::kKeywordKindEndInstr) { + break; + } + + while (valueOfVar.find(";") != CompilerKit::STLString::npos && + keyword.first.keyword_kind != CompilerKit::KeywordKind::kKeywordKindEndInstr) { + valueOfVar.erase(valueOfVar.find(";")); + } + + CompilerKit::STLString varName = text; + + if (keyword.first.keyword_kind == CompilerKit::KeywordKind::kKeywordKindVariableInc) { + varName.erase(varName.find("+=")); + } else if (keyword.first.keyword_kind == + CompilerKit::KeywordKind::kKeywordKindVariableDec) { + varName.erase(varName.find("-=")); + } else if (keyword.first.keyword_kind == + CompilerKit::KeywordKind::kKeywordKindVariableAssign) { + varName.erase(varName.find("=")); + } else if (keyword.first.keyword_kind == CompilerKit::KeywordKind::kKeywordKindEndInstr) { + varName.erase(varName.find(";")); + } + + static Boolean typeFound = false; + + for (auto& keyword : kKeywords) { + if (keyword.keyword_kind == CompilerKit::kKeywordKindType) { + if (text.find(keyword.keyword_name) != CompilerKit::STLString::npos) { + if (text[text.find(keyword.keyword_name)] == ' ') { + typeFound = false; + continue; + } + + typeFound = true; + } + } + } + + CompilerKit::STLString instr = "mov "; + + std::vector newVars; + + if (typeFound && + keyword.first.keyword_kind != CompilerKit::KeywordKind::kKeywordKindVariableInc && + keyword.first.keyword_kind != CompilerKit::KeywordKind::kKeywordKindVariableDec) { + if (kRegisterMap.size() > kRegisterList.size()) { + ++kFunctionEmbedLevel; + } + + while (varName.find(" ") != CompilerKit::STLString::npos) { + varName.erase(varName.find(" "), 1); + } + + while (varName.find("\t") != CompilerKit::STLString::npos) { + varName.erase(varName.find("\t"), 1); + } + + // Remove whitespace only (keep operators and quotes) + while (!valueOfVar.empty() && (valueOfVar[0] == ' ' || valueOfVar[0] == '\t')) { + valueOfVar.erase(0, 1); + } + + constexpr auto kTrueVal = "true"; + constexpr auto kFalseVal = "false"; + + if (valueOfVar == kTrueVal) { + valueOfVar = "1"; + } else if (valueOfVar == kFalseVal) { + valueOfVar = "0"; + } + + std::size_t indexRight = 0UL; + + for (auto pairRight : kRegisterMap) { + ++indexRight; + + if (pairRight != valueOfVar) { + if (valueOfVar[0] == '\"') { + syntax_tree.fUserValue = "segment .data64 __NECTI_LOCAL_VAR_" + varName + ": db " + + valueOfVar + ", 0\n\n"; + syntax_tree.fUserValue += instr + kRegisterList[kRegisterMap.size() - 1] + ", " + + "__NECTI_LOCAL_VAR_" + varName + "\n"; + kOrigin += 1UL; + } else { + syntax_tree.fUserValue = + instr + kRegisterList[kRegisterMap.size() - 1] + ", " + valueOfVar + "\n"; + kOrigin += 1UL; + } + + goto done; + } + } + + if (((int) indexRight - 1) < 0) { + if (valueOfVar[0] == '\"') { + syntax_tree.fUserValue = + "segment .data64 __NECTI_LOCAL_VAR_" + varName + ": db " + valueOfVar + ", 0\n"; + syntax_tree.fUserValue += instr + kRegisterList[kRegisterMap.size()] + ", " + + "__NECTI_LOCAL_VAR_" + varName + "\n"; + kOrigin += 1UL; + } else { + auto mangled = valueOfVar; + + if (mangled.find("(") != std::string::npos) { + auto ret = mangled.erase(mangled.find("(")); + mangled = "__NECTI_"; + mangled += ret; + + syntax_tree.fUserValue = "jmp " + mangled + "\n"; + syntax_tree.fUserValue += + instr + " rax, " + kRegisterList[kRegisterMap.size()] + "\n"; + + goto done; + } + + syntax_tree.fUserValue = + instr + kRegisterList[kRegisterMap.size()] + ", " + mangled + "\n"; + kOrigin += 1UL; + } + + goto done; + } + + if (!valueOfVar.empty() && valueOfVar[0] != '\"' && valueOfVar[0] != '\'' && + !isdigit(valueOfVar[0])) { + for (auto pair : kRegisterMap) { + if (pair == valueOfVar) goto done; + } + + Detail::print_error("Variable not declared: " + varName, file); + break; + } + + done: + for (auto& keyword : kKeywords) { + if (keyword.keyword_kind == CompilerKit::kKeywordKindType && + varName.find(keyword.keyword_name) != CompilerKit::STLString::npos) { + varName.erase(varName.find(keyword.keyword_name), keyword.keyword_name.size()); + break; + } + } + + newVars.push_back(varName); + + break; + } + + kRegisterMap.insert(kRegisterMap.end(), newVars.begin(), newVars.end()); + + if (keyword.second > 0 && + kKeywords[keyword.second - 1].keyword_kind == CompilerKit::kKeywordKindType || + kKeywords[keyword.second - 1].keyword_kind == CompilerKit::kKeywordKindTypePtr) { + syntax_tree.fUserValue = "\n"; + continue; + } + + if (keyword.first.keyword_kind == CompilerKit::KeywordKind::kKeywordKindEndInstr) { + syntax_tree.fUserValue = "\n"; + continue; + } + + if (keyword.first.keyword_kind == CompilerKit::KeywordKind::kKeywordKindVariableInc) { + instr = "add "; + } else if (keyword.first.keyword_kind == + CompilerKit::KeywordKind::kKeywordKindVariableDec) { + instr = "sub "; + } + + CompilerKit::STLString varErrCpy = varName; + + while (varName.find(" ") != CompilerKit::STLString::npos) { + varName.erase(varName.find(" "), 1); + } + + while (varName.find("\t") != CompilerKit::STLString::npos) { + varName.erase(varName.find("\t"), 1); + } + + std::size_t indxReg = 0UL; + + while (!valueOfVar.empty() && (valueOfVar[0] == ' ' || valueOfVar[0] == '\t')) { + valueOfVar.erase(0, 1); + } + + while (valueOfVar.find(" ") != CompilerKit::STLString::npos) { + valueOfVar.erase(valueOfVar.find(" "), 1); + } + + while (valueOfVar.find("\t") != CompilerKit::STLString::npos) { + valueOfVar.erase(valueOfVar.find("\t"), 1); + } + + constexpr auto kTrueVal = "true"; + constexpr auto kFalseVal = "false"; + + /// interpet boolean values, since we're on C++ + + if (valueOfVar == kTrueVal) { + valueOfVar = "1"; + } else if (valueOfVar == kFalseVal) { + valueOfVar = "0"; + } + + for (auto pair : kRegisterMap) { + ++indxReg; + + if (pair != varName) continue; + + std::size_t indexRight = 0ul; + + for (auto pairRight : kRegisterMap) { + ++indexRight; + + if (pairRight != varName) { + syntax_tree.fUserValue = + instr + kRegisterList[kRegisterMap.size()] + ", " + valueOfVar + "\n"; + kOrigin += 1UL; + continue; + } + + syntax_tree.fUserValue = + instr + kRegisterList[indexRight - 1] + ", " + valueOfVar + "\n"; + kOrigin += 1UL; + break; + } + + newVars.push_back(varName); + break; + } + + if (syntax_tree.fUserValue.empty()) { + Detail::print_error("Variable not declared: " + varName, file); + } + + kRegisterMap.insert(kRegisterMap.end(), newVars.begin(), newVars.end()); + + break; + } + case CompilerKit::KeywordKind::kKeywordKindReturn: { + try { + auto pos = text.find("return") + strlen("return") + 1; + CompilerKit::STLString subText = text.substr(pos); + if (subText.find(";") == CompilerKit::STLString::npos) break; + + subText = subText.erase(subText.find(";")); + size_t indxReg = 0UL; + + if (!subText.empty() && subText[0] != '\"' && subText[0] != '\'') { + if (!isdigit(subText[0])) { + for (auto pair : kRegisterMap) { + ++indxReg; + + syntax_tree.fUserValue = "mov rax, " + kRegisterList[indxReg - 1] + "\nret\n"; + kOrigin += 1UL; + + break; + } + } else { + syntax_tree.fUserValue = "mov rax, " + subText + "\nret\n"; + kOrigin += 1UL; + + break; + } + } else { + syntax_tree.fUserValue = "__NECTI_LOCAL_RETURN_STRING: db " + subText + + ", 0\nmov rcx, __NECTI_LOCAL_RETURN_STRING\n"; + syntax_tree.fUserValue += "mov rax, rcx\nret\n"; + kOrigin += 1UL; + + break; + } + + if (syntax_tree.fUserValue.empty()) { + if (subText.find("(") != CompilerKit::STLString::npos) { + subText.erase(subText.find("(")); + + auto it = std::find_if( + kOriginMap.begin(), kOriginMap.end(), + [&subText](std::pair pair) -> bool { + return pair.first.find(subText) != CompilerKit::STLString::npos; + }); + + if (it == kOriginMap.end()) + Detail::print_error("Invalid return value: " + subText, file); + + std::stringstream ss; + ss << it->second; + + syntax_tree.fUserValue = "jmp " + ss.str() + "\nret\n"; + kOrigin += 1UL; + break; + } + } + + syntax_tree.fUserValue = "ret\n"; + kOrigin += 1UL; + + break; + } catch (...) { + syntax_tree.fUserValue = "ret\n"; + kOrigin += 1UL; + + break; + } + } + default: { + continue; + } + } + } + + return syntax_tree; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief C++ assembler class. + */ + +///////////////////////////////////////////////////////////////////////////////////////// + +class AssemblyCPlusPlusInterfaceAMD64 final CK_ASSEMBLY_INTERFACE { + public: + explicit AssemblyCPlusPlusInterfaceAMD64() = default; + ~AssemblyCPlusPlusInterfaceAMD64() override = default; + + NECTI_COPY_DEFAULT(AssemblyCPlusPlusInterfaceAMD64); + + UInt32 Arch() noexcept override { return CompilerKit::AssemblyFactory::kArchAMD64; } + + Int32 CompileToFormat(CompilerKit::STLString src, Int32 arch) override { + if (kFrontend == nullptr) return kExitNO; + + CompilerKit::STLString dest = src; + dest += ".pp.masm"; + + std::ofstream out_fp(dest); + std::ifstream src_fp = std::ifstream(src + ".pp"); + + CompilerKit::STLString line_source; + + out_fp << "%bits 64\n"; + out_fp << "%org " << kOrigin << "\n\n"; + + while (std::getline(src_fp, line_source)) { + out_fp << kFrontend->Compile(line_source, src).fUserValue; + } + + return kExitOK; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////////////// + +#define kExtListCxx \ + { ".cpp", ".cxx", ".cc", ".c++", ".cp" } + +NECTI_MODULE(CompilerCPlusPlusAMD64) { + Boolean skip = false; + + kKeywords.emplace_back("if", CompilerKit::kKeywordKindIf); + kKeywords.emplace_back("else", CompilerKit::kKeywordKindElse); + kKeywords.emplace_back("else if", CompilerKit::kKeywordKindElseIf); + + kKeywords.emplace_back("class", CompilerKit::kKeywordKindClass); + kKeywords.emplace_back("struct", CompilerKit::kKeywordKindClass); + kKeywords.emplace_back("namespace", CompilerKit::kKeywordKindNamespace); + kKeywords.emplace_back("typedef", CompilerKit::kKeywordKindTypedef); + kKeywords.emplace_back("using", CompilerKit::kKeywordKindTypedef); + kKeywords.emplace_back("{", CompilerKit::kKeywordKindBodyStart); + kKeywords.emplace_back("}", CompilerKit::kKeywordKindBodyEnd); + kKeywords.emplace_back("auto", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("int", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("bool", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("unsigned", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("short", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("char", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("long", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("float", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("double", CompilerKit::kKeywordKindType); + kKeywords.emplace_back("void", CompilerKit::kKeywordKindType); + + kKeywords.emplace_back("auto*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("int*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("bool*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("unsigned*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("short*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("char*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("long*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("float*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("double*", CompilerKit::kKeywordKindTypePtr); + kKeywords.emplace_back("void*", CompilerKit::kKeywordKindTypePtr); + + kKeywords.emplace_back("(", CompilerKit::kKeywordKindFunctionStart); + kKeywords.emplace_back(")", CompilerKit::kKeywordKindFunctionEnd); + kKeywords.emplace_back("=", CompilerKit::kKeywordKindVariableAssign); + kKeywords.emplace_back("+=", CompilerKit::kKeywordKindVariableInc); + kKeywords.emplace_back("-=", CompilerKit::kKeywordKindVariableDec); + kKeywords.emplace_back("const", CompilerKit::kKeywordKindConstant); + kKeywords.emplace_back("*", CompilerKit::kKeywordKindPtr); + kKeywords.emplace_back("->", CompilerKit::kKeywordKindPtrAccess); + kKeywords.emplace_back(".", CompilerKit::kKeywordKindAccess); + kKeywords.emplace_back(",", CompilerKit::kKeywordKindArgSeparator); + kKeywords.emplace_back(";", CompilerKit::kKeywordKindEndInstr); + kKeywords.emplace_back(":", CompilerKit::kKeywordKindSpecifier); + kKeywords.emplace_back("public:", CompilerKit::kKeywordKindSpecifier); + kKeywords.emplace_back("private:", CompilerKit::kKeywordKindSpecifier); + kKeywords.emplace_back("protected:", CompilerKit::kKeywordKindSpecifier); + kKeywords.emplace_back("final", CompilerKit::kKeywordKindSpecifier); + kKeywords.emplace_back("return", CompilerKit::kKeywordKindReturn); + kKeywords.emplace_back("/*", CompilerKit::kKeywordKindCommentMultiLineStart); + kKeywords.emplace_back("*/", CompilerKit::kKeywordKindCommentMultiLineEnd); + kKeywords.emplace_back("//", CompilerKit::kKeywordKindCommentInline); + kKeywords.emplace_back("==", CompilerKit::kKeywordKindEq); + kKeywords.emplace_back("!=", CompilerKit::kKeywordKindNotEq); + kKeywords.emplace_back(">=", CompilerKit::kKeywordKindGreaterEq); + kKeywords.emplace_back("<=", CompilerKit::kKeywordKindLessEq); + + kErrorLimit = 0; + + kFrontend = new CompilerFrontendCPlusPlusAMD64(); + kAssembler.Mount(new AssemblyCPlusPlusInterfaceAMD64()); + + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + + // Ensure cleanup on exit + std::atexit([]() { + delete kFrontend; + kFrontend = nullptr; + }); + + for (auto index = 1UL; index < argc; ++index) { + if (!argv[index]) break; + + if (argv[index][0] == '-') { + if (skip) { + skip = false; + continue; + } + + if (strcmp(argv[index], "-cxx-verbose") == 0) { + kVerbose = true; + continue; + } + + if (strcmp(argv[index], "-cxx-dialect") == 0) { + if (kFrontend) std::cout << kFrontend->Language() << "\n"; + + return NECTI_SUCCESS; + } + + if (strcmp(argv[index], "-cxx-max-err") == 0) { + try { + kErrorLimit = std::strtol(argv[index + 1], nullptr, 10); + } + // catch anything here + catch (...) { + kErrorLimit = 0; + } + + skip = true; + + continue; + } + + CompilerKit::STLString err = "Unknown option: "; + err += argv[index]; + + Detail::print_error(err, "cxxdrv"); + + continue; + } + + CompilerKit::STLString argv_i = argv[index]; + + std::vector exts = kExtListCxx; + + for (CompilerKit::STLString ext : exts) { + if (argv_i.ends_with(ext)) { + if (kAssembler.Compile(argv_i, kMachine) != kExitOK) { + return NECTI_INVALID_DATA; + } + + break; + } + } + } + + kAssembler.Unmount(); + + return NECTI_SUCCESS; +} + +// +// Last rev 25-8-7 +// diff --git a/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc b/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc new file mode 100644 index 0000000..8a503ed --- /dev/null +++ b/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc @@ -0,0 +1,678 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + + @file DynamicLinker64PEF.cc + @brief: C++ 64-Bit PEF Linker for NeKernel.org's NeKernel + +======================================== */ + +/// @author Amlal El Mahrouss (amlal@nekernel.org) +/// @brief NeKernel.org 64-bit PEF Linker. +/// Last Rev: Sat Apr 19 CET 2025 +/// @note Do not look up for anything with .code64/.data64/.zero64! +/// It will be loaded when the program loader will start the image. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define kLinkerVersionStr "NeCTI 64-Bit Linker (Preferred Executable Format)" + +#define kPefNoCpu (0U) +#define kPefNoSubCpu (0U) + +#define kLinkerDefaultOrigin kPefBaseOrigin +#define kLinkerId (0x5046FF) +#define kLinkerAbiContainer "__PEFContainer:ABI:" + +#define kPrintF printf +#define kLinkerSplash() kConsoleOut << kLinkerVersionStr << std::endl + +/// @brief PEF stack size symbol. +#define kLinkerStackSizeSymbol "__PEFSizeOfReserveStack" + +#define kConsoleOut \ + (std::cout << "\e[0;31m" \ + << "ld64: " \ + << "\e[0;97m") + +enum { + kABITypeNull = 0, + kABITypeStart = 0x1010, /* The start of ABI list. */ + kABITypeNE = 0x5046, /* PF (NeKernel.org's PEF ABI) */ + kABITypeInvalid = 0xFFFF, +}; + +static CompilerKit::STLString kOutput = "a" kPefExt; +static Int32 kAbi = kABITypeNE; +static Int32 kSubArch = kPefNoSubCpu; +static Int32 kArch = CompilerKit::kPefArchInvalid; +static Bool kFatBinaryEnable = false; +static Bool kStartFound = false; +static Bool kDuplicateSymbols = false; + +/* ld64 is to be found, mld is to be found at runtime. */ +static const Char* kLdDefineSymbol = ":UndefinedSymbol:"; +static const Char* kLdDynamicSym = ":RuntimeSymbol:"; + +static CompilerKit::STLString kLinkerStart = kPefStart; + +/* object code and list. */ +static std::vector kObjectList; +static std::vector kObjectBytes; + +/// @brief NE 64-bit Linker. +/// @note This linker is made for PEF executable, thus NE based OSes. +NECTI_MODULE(DynamicLinker64PEF) { + bool is_executable = true; + + CompilerKit::install_signal(SIGSEGV, Detail::drvi_crash_handler); + + /** + * @brief parse flags and trigger options. + */ + for (size_t linker_arg = 1; linker_arg < argc; ++linker_arg) { + if (std::strcmp(argv[linker_arg], "-help") == 0) { + kLinkerSplash(); + + kConsoleOut << "-version: Show linker version.\n"; + kConsoleOut << "-help: Show linker help.\n"; + kConsoleOut << "-verbose: Enable linker trace.\n"; + kConsoleOut << "-dylib: Output as a Dynamic PEF.\n"; + kConsoleOut << "-fat: Output as a FAT PEF.\n"; + kConsoleOut << "-32k: Output as a 32x0 PEF.\n"; + kConsoleOut << "-64k: Output as a 64x0 PEF.\n"; + kConsoleOut << "-amd64: Output as a AMD64 PEF.\n"; + kConsoleOut << "-rv64: Output as a RISC-V PEF.\n"; + kConsoleOut << "-power64: Output as a POWER PEF.\n"; + kConsoleOut << "-arm64: Output as a ARM64 PEF.\n"; + kConsoleOut << "-output: Select the output file name.\n"; + + return NECTI_SUCCESS; + } else if (std::strcmp(argv[linker_arg], "-version") == 0) { + kLinkerSplash(); + + return NECTI_SUCCESS; + } else if (std::strcmp(argv[linker_arg], "-fat") == 0) { + kFatBinaryEnable = true; + + continue; + } else if (std::strcmp(argv[linker_arg], "-64k") == 0) { + kArch = CompilerKit::kPefArch64000; + + continue; + } else if (std::strcmp(argv[linker_arg], "-amd64") == 0) { + kArch = CompilerKit::kPefArchAMD64; + + continue; + } else if (std::strcmp(argv[linker_arg], "-start") == 0) { + if (argv[linker_arg + 1] == nullptr || argv[linker_arg + 1][0] == '-') continue; + + kLinkerStart = argv[linker_arg + 1]; + linker_arg += 1; + + continue; + } else if (std::strcmp(argv[linker_arg], "-32k") == 0) { + kArch = CompilerKit::kPefArch32000; + + continue; + } else if (std::strcmp(argv[linker_arg], "-power64") == 0) { + kArch = CompilerKit::kPefArchPowerPC; + + continue; + } else if (std::strcmp(argv[linker_arg], "-riscv64") == 0) { + kArch = CompilerKit::kPefArchRISCV; + + continue; + } else if (std::strcmp(argv[linker_arg], "-arm64") == 0) { + kArch = CompilerKit::kPefArchARM64; + + continue; + } else if (std::strcmp(argv[linker_arg], "-verbose") == 0) { + kVerbose = true; + + continue; + } else if (std::strcmp(argv[linker_arg], "-dylib") == 0) { + if (kOutput.empty()) { + continue; + } + + if (kOutput.find(kPefExt) != CompilerKit::STLString::npos) + kOutput.erase(kOutput.find(kPefExt), strlen(kPefExt)); + + kOutput += kPefDylibExt; + + is_executable = false; + + continue; + } else if (std::strcmp(argv[linker_arg], "-output") == 0) { + if ((linker_arg + 1) > argc) continue; + + kOutput = argv[linker_arg + 1]; + ++linker_arg; + + continue; + } else { + if (argv[linker_arg][0] == '-') { + kConsoleOut << "unknown flag: " << argv[linker_arg] << "\n"; + return EXIT_FAILURE; + } + + kObjectList.emplace_back(argv[linker_arg]); + + continue; + } + } + + if (kOutput.empty()) { + kConsoleOut << "no output filename set." << std::endl; + return NECTI_EXEC_ERROR; + } else if (kObjectList.empty()) { + kConsoleOut << "no input files." << std::endl; + return NECTI_EXEC_ERROR; + } else { + namespace FS = std::filesystem; + + // check for existing files, if they don't throw an error. + for (auto& obj : kObjectList) { + if (!FS::exists(obj)) { + // if filesystem doesn't find file + // -> throw error. + kConsoleOut << "no such file: " << obj << std::endl; + return NECTI_EXEC_ERROR; + } + } + } + + // PEF expects a valid target architecture when outputing a binary. + if (kArch == CompilerKit::kPefArchInvalid) { + kConsoleOut << "no target architecture set, can't continue." << std::endl; + return NECTI_EXEC_ERROR; + } + + CompilerKit::PEFContainer pef_container{}; + + int32_t archs = kArch; + + pef_container.Count = 0UL; + pef_container.Kind = is_executable ? CompilerKit::kPefKindExec : CompilerKit::kPefKindDylib; + pef_container.SubCpu = kSubArch; + pef_container.Linker = kLinkerId; // Amlal El Mahrouss Linker + pef_container.Abi = kAbi; // Multi-Processor UX ABI + pef_container.Magic[0] = kPefMagic[kFatBinaryEnable ? 2 : 0]; + pef_container.Magic[1] = kPefMagic[1]; + pef_container.Magic[2] = kPefMagic[kFatBinaryEnable ? 0 : 2]; + pef_container.Magic[3] = kPefMagic[3]; + pef_container.Version = kPefVersion; + + // specify the start address, can be 0x10000 + pef_container.Start = kLinkerDefaultOrigin; + pef_container.HdrSz = sizeof(CompilerKit::PEFContainer); + pef_container.Checksum = 0UL; + + std::ofstream output_fc(kOutput, std::ofstream::binary); + + if (output_fc.bad()) { + if (kVerbose) { + kConsoleOut << "error: " << strerror(errno) << "\n"; + } + + return NECTI_FILE_NOT_FOUND; + } + + //! Read AE to convert as PEF. + + std::vector command_headers; + CompilerKit::Utils::AEReadableProtocol reader_protocol{}; + + for (const auto& objectFile : kObjectList) { + if (!std::filesystem::exists(objectFile)) continue; + + CompilerKit::AEHeader hdr{}; + + reader_protocol.file_pointer_ = std::ifstream(objectFile, std::ifstream::binary); + reader_protocol.file_pointer_ >> hdr; + + if (hdr.fMagic[0] == kAEMag0 && hdr.fMagic[1] == kAEMag1 && + hdr.fSize == sizeof(CompilerKit::AEHeader)) { + if (hdr.fArch != kArch) { + if (kVerbose) kConsoleOut << "is this a FAT binary? : "; + + if (!kFatBinaryEnable) { + if (kVerbose) kConsoleOut << "not a FAT binary.\n"; + + kConsoleOut << "object " << objectFile + << " is a different kind of architecture and output isn't " + "treated as a FAT binary." + << std::endl; + + return NECTI_FAT_ERROR; + } else { + if (kVerbose) { + kConsoleOut << "Architecture matches what we expect.\n"; + } + } + } + + // append arch type to archs varaible. + archs |= hdr.fArch; + std::size_t cnt = hdr.fCount; + + if (kVerbose) kConsoleOut << "header found, record count: " << cnt << "\n"; + + pef_container.Count = cnt; + + Char* raw_ae_records = new Char[cnt * sizeof(CompilerKit::AERecordHeader)]; + + if (!raw_ae_records) { + if (kVerbose) kConsoleOut << "allocation failed for records of count: " << cnt << "\n"; + } + + std::memset(raw_ae_records, 0, cnt * sizeof(CompilerKit::AERecordHeader)); + + auto* ae_records = reader_protocol.Read(raw_ae_records, cnt); + + size_t org = kLinkerDefaultOrigin; + + for (size_t ae_record_index = 0; ae_record_index < cnt; ++ae_record_index) { + CompilerKit::PEFCommandHeader command_header{0}; + std::size_t offset_of_obj = ae_records[ae_record_index].fOffset; + + std::memcpy(command_header.Name, ae_records[ae_record_index].fName, kPefNameLen); + + CompilerKit::STLString cmd_hdr_name(command_header.Name); + + // check this header if it's any valid. + if (cmd_hdr_name.find(kPefCode64) == CompilerKit::STLString::npos && + cmd_hdr_name.find(kPefData64) == CompilerKit::STLString::npos && + cmd_hdr_name.find(kPefZero64) == CompilerKit::STLString::npos) { + if (cmd_hdr_name.find(kLinkerStart) == CompilerKit::STLString::npos && + *command_header.Name == 0) { + if (cmd_hdr_name.find(kLdDefineSymbol) != CompilerKit::STLString::npos) { + goto ld_mark_header; + } else { + continue; + } + } + } + + if (cmd_hdr_name.find(kLinkerStart) != CompilerKit::STLString::npos && + cmd_hdr_name.find(kPefCode64) != CompilerKit::STLString::npos) { + kStartFound = true; + } + + ld_mark_header: + command_header.Offset = offset_of_obj; + command_header.Kind = ae_records[ae_record_index].fKind; + command_header.VirtualSize = ae_records[ae_record_index].fSize; + command_header.Cpu = hdr.fArch; + command_header.VirtualAddress = org; + command_header.SubCpu = hdr.fSubArch; + command_header.OffsetSize = ae_records[ae_record_index].fSize; + + org += command_header.VirtualSize; + + if (kVerbose) { + kConsoleOut << "Record: " << ae_records[ae_record_index].fName << " is marked.\n"; + kConsoleOut << "Offset: " << command_header.Offset << "\n"; + } + + command_headers.emplace_back(command_header); + } + + delete[] raw_ae_records; + raw_ae_records = nullptr; + + std::vector bytes; + bytes.resize(hdr.fCodeSize); + + reader_protocol.file_pointer_.seekg(std::streamsize(hdr.fStartCode)); + reader_protocol.file_pointer_.read(bytes.data(), std::streamsize(hdr.fCodeSize)); + + kObjectBytes.push_back({.mBlob = bytes, .mOffset = hdr.fStartCode}); + + // Blob was written, close fp. + + reader_protocol.file_pointer_.close(); + + continue; + } + + kConsoleOut << "not an object container: " << objectFile << std::endl; + + // don't continue, it is a fatal error. + return NECTI_EXEC_ERROR; + } + + pef_container.Cpu = archs; + + output_fc << pef_container; + + if (kVerbose) { + kConsoleOut << "wrote container to: " << output_fc.tellp() << ".\n"; + } + + output_fc.seekp(std::streamsize(pef_container.HdrSz)); + + std::vector not_found; + std::vector symbols; + + // step 2: check for errors (multiple symbols, undefined ones) + + CompilerKit::STLString cmd_hdr_name; + + for (auto& command_hdr : command_headers) { + // check if this symbol needs to be resolved. + if (CompilerKit::STLString(command_hdr.Name).find(kLdDefineSymbol) != + CompilerKit::STLString::npos && + CompilerKit::STLString(command_hdr.Name).find(kLdDynamicSym) == + CompilerKit::STLString::npos) { + if (kVerbose) kConsoleOut << "Found undefined symbol: " << command_hdr.Name << "\n"; + + cmd_hdr_name = command_hdr.Name; + + if (auto it = std::find(not_found.begin(), not_found.end(), cmd_hdr_name); + it == not_found.end()) { + not_found.emplace_back(cmd_hdr_name); + } + } + + symbols.emplace_back(command_hdr.Name); + } + + // Now try to solve these symbols. + + for (size_t not_found_idx = 0; not_found_idx < command_headers.size(); ++not_found_idx) { + if (const auto it = std::find(not_found.begin(), not_found.end(), + CompilerKit::STLString(command_headers[not_found_idx].Name)); + it != not_found.end()) { + CompilerKit::STLString symbol_imp = *it; + + if (symbol_imp.find(kLdDefineSymbol) == CompilerKit::STLString::npos) continue; + + // erase the lookup prefix. + symbol_imp.erase(0, symbol_imp.find(kLdDefineSymbol) + strlen(kLdDefineSymbol)); + + // demangle everything. + while (symbol_imp.find('$') != CompilerKit::STLString::npos) + symbol_imp.erase(symbol_imp.find('$'), 1); + + // the reason we do is because, this may not match the symbol, and we need + // to look for other matching symbols. + for (auto& command_hdr : command_headers) { + if (CompilerKit::STLString(command_hdr.Name).find(symbol_imp) != + CompilerKit::STLString::npos && + CompilerKit::STLString(command_hdr.Name).find(kLdDefineSymbol) == + CompilerKit::STLString::npos) { + CompilerKit::STLString undefined_symbol = command_hdr.Name; + auto result_of_sym = undefined_symbol.substr(undefined_symbol.find(symbol_imp)); + + for (int i = 0; result_of_sym[i] != 0; ++i) { + if (result_of_sym[i] != symbol_imp[i]) goto ld_continue_search; + } + + not_found.erase(it); + + if (kVerbose) kConsoleOut << "Found symbol: " << command_hdr.Name << "\n"; + + break; + } + } + + ld_continue_search: + continue; + } + } + + // step 3: check for errors (recheck if we have those symbols.) + + if (!kStartFound && is_executable) { + if (kVerbose) + kConsoleOut << "Undefined entrypoint: " << kLinkerStart + << ", you may have forget to link " + "against the C++ runtime library.\n"; + + kConsoleOut << "Undefined entrypoint " << kLinkerStart << " for executable: " << kOutput + << "\n"; + } + + // step 4: write all PEF commands. + + CompilerKit::PEFCommandHeader date_cmd_hdr{}; + + time_t timestamp = time(nullptr); + + CompilerKit::STLString timeStampStr = "Container:BuildEpoch:"; + timeStampStr += std::to_string(timestamp); + + strncpy(date_cmd_hdr.Name, timeStampStr.c_str(), timeStampStr.size()); + + date_cmd_hdr.Flags = 0; + date_cmd_hdr.Kind = CompilerKit::kPefZero; + date_cmd_hdr.Offset = output_fc.tellp(); + date_cmd_hdr.VirtualSize = timeStampStr.size(); + + command_headers.push_back(date_cmd_hdr); + + CompilerKit::PEFCommandHeader abi_cmd_hdr{}; + + CompilerKit::STLString abi = kLinkerAbiContainer; + + switch (kArch) { + case CompilerKit::kPefArchAMD64: { + abi += "MSFT"; + break; + } + case CompilerKit::kPefArchPowerPC: { + abi += "SYSV"; + break; + } + case CompilerKit::kPefArch32000: + case CompilerKit::kPefArch64000: { + abi += "_NEP"; + break; + } + default: { + abi += "_IDK"; + break; + } + } + + std::memcpy(abi_cmd_hdr.Name, abi.c_str(), abi.size()); + + abi_cmd_hdr.VirtualSize = abi.size(); + abi_cmd_hdr.Offset = output_fc.tellp(); + abi_cmd_hdr.Flags = 0; + abi_cmd_hdr.Kind = CompilerKit::kPefLinkerID; + + command_headers.push_back(abi_cmd_hdr); + + CompilerKit::PEFCommandHeader stack_cmd_hdr{0}; + + stack_cmd_hdr.Cpu = kArch; + stack_cmd_hdr.Flags = 0; + stack_cmd_hdr.VirtualSize = sizeof(uintptr_t); + stack_cmd_hdr.Offset = 0; + + std::memcpy(stack_cmd_hdr.Name, kLinkerStackSizeSymbol, strlen(kLinkerStackSizeSymbol)); + + command_headers.push_back(stack_cmd_hdr); + + CompilerKit::PEFCommandHeader uuid_cmd_hdr{}; + + std::random_device rd; + + auto seedData = std::array{}; + std::generate(std::begin(seedData), std::end(seedData), std::ref(rd)); + std::seed_seq seq(std::begin(seedData), std::end(seedData)); + std::mt19937 generator(seq); + + auto gen = uuids::uuid_random_generator{generator}; + uuids::uuid id = gen(); + auto uuidStr = uuids::to_string(id); + + std::memcpy(uuid_cmd_hdr.Name, "Container:GUID:4:", strlen("Container:GUID:4:")); + std::memcpy(uuid_cmd_hdr.Name + strlen("Container:GUID:4:"), uuidStr.c_str(), uuidStr.size()); + + uuid_cmd_hdr.VirtualSize = strlen(uuid_cmd_hdr.Name); + uuid_cmd_hdr.Offset = output_fc.tellp(); + uuid_cmd_hdr.Flags = CompilerKit::kPefLinkerID; + uuid_cmd_hdr.Kind = CompilerKit::kPefZero; + + command_headers.push_back(uuid_cmd_hdr); + + // prepare a symbol vector. + std::vector undef_symbols; + std::vector dupl_symbols; + std::vector resolve_symbols; + + constexpr Int32 kPaddingOffset = 16; + + size_t previous_offset = + (command_headers.size() * sizeof(CompilerKit::PEFCommandHeader)) + kPaddingOffset; + + CompilerKit::PEFCommandHeader end_exec_hdr; + + end_exec_hdr.Offset = output_fc.tellp(); + end_exec_hdr.Flags = CompilerKit::kPefLinkerID; + end_exec_hdr.Kind = CompilerKit::kPefZero; + + std::memcpy(end_exec_hdr.Name, "Container:Exec:END", strlen("Container:Exec:END")); + + end_exec_hdr.VirtualSize = strlen(end_exec_hdr.Name); + + command_headers.push_back(end_exec_hdr); + + // Finally write down the command headers. + // And check for any duplications + for (size_t commandHeaderIndex = 0UL; commandHeaderIndex < command_headers.size(); + ++commandHeaderIndex) { + if (CompilerKit::STLString(command_headers[commandHeaderIndex].Name).find(kLdDefineSymbol) != + CompilerKit::STLString::npos && + CompilerKit::STLString(command_headers[commandHeaderIndex].Name).find(kLdDynamicSym) == + CompilerKit::STLString::npos) { + // ignore :UndefinedSymbol: headers, they do not contain code. + continue; + } + + CompilerKit::STLString symbol_name = command_headers[commandHeaderIndex].Name; + + if (!symbol_name.empty()) { + undef_symbols.emplace_back(symbol_name); + } + + command_headers[commandHeaderIndex].Offset += previous_offset; + previous_offset += command_headers[commandHeaderIndex].VirtualSize; + + CompilerKit::STLString name = command_headers[commandHeaderIndex].Name; + + /// so this is valid when we get to the entrypoint. + /// it is always a code64 container. And should equal to kLinkerStart as well. + /// this chunk of code updates the pef_container.Start with the updated offset. + if (name.find(kLinkerStart) != CompilerKit::STLString::npos && + name.find(kPefCode64) != CompilerKit::STLString::npos) { + pef_container.Start = command_headers[commandHeaderIndex].Offset; + auto tellCurPos = output_fc.tellp(); + + output_fc.seekp(0); + output_fc << pef_container; + + output_fc.seekp(tellCurPos); + } + + if (kVerbose) { + kConsoleOut << "Command name: " << name << "\n"; + kConsoleOut << "VirtualAddress of command content: " + << command_headers[commandHeaderIndex].Offset << "\n"; + } + + output_fc << command_headers[commandHeaderIndex]; + + for (size_t sub_command_header_index = 0UL; sub_command_header_index < command_headers.size(); + ++sub_command_header_index) { + if (sub_command_header_index == commandHeaderIndex) continue; + + if (CompilerKit::STLString(command_headers[sub_command_header_index].Name) + .find(kLdDefineSymbol) != CompilerKit::STLString::npos && + CompilerKit::STLString(command_headers[sub_command_header_index].Name) + .find(kLdDynamicSym) == CompilerKit::STLString::npos) { + if (kVerbose) { + kConsoleOut << "Ignoring :UndefinedSymbol: headers...\n"; + } + + // ignore :UndefinedSymbol: headers, they do not contain code. + continue; + } + + auto& command_hdr = command_headers[sub_command_header_index]; + + if (command_hdr.Name == CompilerKit::STLString(command_headers[commandHeaderIndex].Name)) { + if (std::find(dupl_symbols.cbegin(), dupl_symbols.cend(), command_hdr.Name) == + dupl_symbols.cend()) { + dupl_symbols.emplace_back(command_hdr.Name); + } + + if (kVerbose) kConsoleOut << "Found duplicate symbols of: " << command_hdr.Name << "\n"; + + kDuplicateSymbols = true; + } + } + } + + if (!dupl_symbols.empty()) { + for (auto& symbol : dupl_symbols) { + kConsoleOut << "Multiple symbols of: " << symbol << " detected, cannot continue.\n"; + } + + return NECTI_EXEC_ERROR; + } + + // step 2.5: write program bytes. + + for (auto& struct_of_blob : kObjectBytes) { + output_fc.write(struct_of_blob.mBlob.data(), struct_of_blob.mBlob.size()); + } + + if (kVerbose) { + kConsoleOut << "Wrote contents of: " << kOutput << "\n"; + } + + // step 3: check if we have those symbols + + std::vector unreferenced_symbols; + + for (auto& command_hdr : command_headers) { + if (auto it = + std::find(not_found.begin(), not_found.end(), CompilerKit::STLString(command_hdr.Name)); + it != not_found.end()) { + unreferenced_symbols.emplace_back(command_hdr.Name); + } + } + + if (!unreferenced_symbols.empty()) { + for (auto& unreferenced_symbol : unreferenced_symbols) { + kConsoleOut << "Undefined symbol " << unreferenced_symbol << "\n"; + } + + return NECTI_EXEC_ERROR; + } + + if ((!kStartFound || kDuplicateSymbols) && + (std::filesystem::exists(kOutput) || !unreferenced_symbols.empty())) { + if (kVerbose) { + kConsoleOut << "File: " << kOutput << " is corrupt now...\n"; + } + + return NECTI_EXEC_ERROR; + } + + return NECTI_SUCCESS; +} + +// Last rev 13-1-24 diff --git a/src/CompilerKit/src/Macro/CPlusPlusPreprocessor.cc b/src/CompilerKit/src/Macro/CPlusPlusPreprocessor.cc new file mode 100644 index 0000000..ed53782 --- /dev/null +++ b/src/CompilerKit/src/Macro/CPlusPlusPreprocessor.cc @@ -0,0 +1,893 @@ +/* + * ======================================================== + * + * C++ Preprocessor Driver + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +/// BUGS: 0 + +#include +#include +#include +#include +#include +#include +#include +#include + +#define kMacroPrefix '#' + +/// @author Amlal El Mahrouss (amlal@nekernel.org) +/// @file CPlusPlusPreprocessor.cc +/// @brief Preprocessor. + +typedef Int32 (*bpp_parser_fn_t)(CompilerKit::STLString& line, std::ifstream& hdr_file, + std::ofstream& pp_out); + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief Preprocessor internal types. + +///////////////////////////////////////////////////////////////////////////////////////// + +namespace Detail { +enum { + kInvalid = 0, + kEqual = 100, + kGreaterEqThan, + kLesserEqThan, + kGreaterThan, + kLesserThan, + kNotEqual, + kCount = 6, +}; + +struct bpp_macro_condition final { + int32_t fType; + CompilerKit::STLString fTypeName; + + void Print() { + std::cout << "type: " << fType << "\n"; + std::cout << "type_name: " << fTypeName << "\n"; + } +}; + +struct bpp_macro final { + std::vector fArgs; + CompilerKit::STLString fName; + CompilerKit::STLString fValue; + + void Print() { + std::cout << "name: " << fName << "\n"; + std::cout << "value: " << fValue << "\n"; + + for (auto& arg : fArgs) { + std::cout << "arg: " << arg << "\n"; + } + } +}; +} // namespace Detail + +static std::vector kFiles; +static std::vector kMacros; +static std::vector kIncludes; + +static CompilerKit::STLString kWorkingDir = ""; + +///////////////////////////////////////////////////////////////////////////////////////// + +// @name bpp_parse_if_condition +// @brief parse #if condition + +///////////////////////////////////////////////////////////////////////////////////////// + +int32_t bpp_parse_if_condition(Detail::bpp_macro_condition& cond, Detail::bpp_macro& macro, + bool& inactive_code, bool& defined, + CompilerKit::STLString& macro_str) { + if (cond.fType == Detail::kEqual) { + auto substr_macro = macro_str.substr(macro_str.find(macro.fName) + macro.fName.size()); + + if (substr_macro.find(macro.fValue) != CompilerKit::STLString::npos) { + if (macro.fValue == "0") { + defined = false; + inactive_code = true; + + return 1; + } + + defined = true; + inactive_code = false; + + return 1; + } + } else if (cond.fType == Detail::kNotEqual) { + auto substr_macro = macro_str.substr(macro_str.find(macro.fName) + macro.fName.size()); + + if (substr_macro.find(macro.fName) != CompilerKit::STLString::npos) { + if (substr_macro.find(macro.fValue) != CompilerKit::STLString::npos) { + defined = false; + inactive_code = true; + + return 1; + } + + defined = true; + inactive_code = false; + + return 1; + } + + return 0; + } + + auto substr_macro = macro_str.substr(macro_str.find(macro.fName) + macro.fName.size()); + + CompilerKit::STLString number; + + for (auto& macro_num : kMacros) { + if (substr_macro.find(macro_num.fName) != CompilerKit::STLString::npos) { + for (size_t i = 0; i < macro_num.fName.size(); ++i) { + if (isdigit(macro_num.fValue[i])) { + number += macro_num.fValue[i]; + } else { + number.clear(); + break; + } + } + + break; + } + } + + size_t y = 2; + + /* last try */ + for (; y < macro_str.size(); y++) { + if (isdigit(macro_str[y])) { + for (size_t x = y; x < macro_str.size(); x++) { + if (macro_str[x] == ' ') break; + + number += macro_str[x]; + } + + break; + } + } + + size_t rhs = atol(macro.fValue.c_str()); + size_t lhs = atol(number.c_str()); + + if (lhs == 0) { + number.clear(); + ++y; + + for (; y < macro_str.size(); y++) { + if (isdigit(macro_str[y])) { + for (size_t x = y; x < macro_str.size(); x++) { + if (macro_str[x] == ' ') break; + + number += macro_str[x]; + } + + break; + } + } + + lhs = atol(number.c_str()); + } + + if (cond.fType == Detail::kGreaterThan) { + if (lhs < rhs) { + defined = true; + inactive_code = false; + + return 1; + } + + return 0; + } + + if (cond.fType == Detail::kGreaterEqThan) { + if (lhs <= rhs) { + defined = true; + inactive_code = false; + + return 1; + } + + return 0; + } + + if (cond.fType == Detail::kLesserEqThan) { + if (lhs >= rhs) { + defined = true; + inactive_code = false; + + return 1; + } + + return 0; + } + + if (cond.fType == Detail::kLesserThan) { + if (lhs > rhs) { + defined = true; + inactive_code = false; + + return 1; + } + + return 0; + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief stores every included file here. + +///////////////////////////////////////////////////////////////////////////////////////// + +std::vector kAllIncludes; + +///////////////////////////////////////////////////////////////////////////////////////// + +// @name bpp_parse_file +// @brief parse file to preprocess it. + +///////////////////////////////////////////////////////////////////////////////////////// + +void bpp_parse_file(std::ifstream& hdr_file, std::ofstream& pp_out) { + CompilerKit::STLString hdr_line; + CompilerKit::STLString line_after_include; + + bool inactive_code = false; + bool defined = false; + + try { + while (std::getline(hdr_file, hdr_line)) { + if (inactive_code) { + if (hdr_line.find("#endif") == CompilerKit::STLString::npos) { + continue; + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("#endif") != CompilerKit::STLString::npos) { + inactive_code = false; + } + } + + if (hdr_line.find("*/") != CompilerKit::STLString::npos) { + hdr_line.erase(hdr_line.find("*/"), strlen("*/")); + } + + if (hdr_line.find("/*") != CompilerKit::STLString::npos) { + inactive_code = true; + + // get rid of comment. + hdr_line.erase(hdr_line.find("/*")); + } + + if (hdr_line[0] == kMacroPrefix && hdr_line.find("endif") != CompilerKit::STLString::npos) { + if (!defined && inactive_code) { + inactive_code = false; + defined = false; + + continue; + } + + continue; + } + + if (!defined && inactive_code) { + continue; + } + + if (defined && inactive_code) { + continue; + } + + for (auto macro : kMacros) { + if (CompilerKit::find_word(hdr_line, macro.fName)) { + if (hdr_line.substr(hdr_line.find(macro.fName)).find(macro.fName + '(') != + CompilerKit::STLString::npos) { + if (!macro.fArgs.empty()) { + CompilerKit::STLString symbol_val = macro.fValue; + std::vector args; + + size_t x_arg_indx = 0; + + CompilerKit::STLString line_after_define = hdr_line; + CompilerKit::STLString str_arg; + + if (line_after_define.find("(") != CompilerKit::STLString::npos) { + line_after_define.erase(0, line_after_define.find("(") + 1); + + for (auto& subc : line_after_define) { + if (subc == ' ' || subc == '\t') continue; + + if (subc == ',' || subc == ')') { + if (str_arg.empty()) continue; + + args.push_back(str_arg); + + str_arg.clear(); + + continue; + } + + str_arg.push_back(subc); + } + } + + for (auto arg : macro.fArgs) { + if (symbol_val.find(macro.fArgs[x_arg_indx]) != CompilerKit::STLString::npos) { + symbol_val.replace(symbol_val.find(macro.fArgs[x_arg_indx]), + macro.fArgs[x_arg_indx].size(), args[x_arg_indx]); + ++x_arg_indx; + } else { + throw std::runtime_error("cppdrv: Internal error."); + } + } + + auto len = macro.fName.size(); + len += symbol_val.size(); + len += 2; // ( and ) + + hdr_line.erase(hdr_line.find(")"), 1); + + hdr_line.replace(hdr_line.find(hdr_line.substr(hdr_line.find(macro.fName + '('))), + len, symbol_val); + } else { + auto value = macro.fValue; + + hdr_line.replace(hdr_line.find(macro.fName), macro.fName.size(), value); + } + } + } + } + + if (hdr_line[0] == kMacroPrefix && hdr_line.find("define ") != CompilerKit::STLString::npos) { + auto line_after_define = hdr_line.substr(hdr_line.find("define ") + strlen("define ")); + + CompilerKit::STLString macro_value; + CompilerKit::STLString macro_key; + + std::size_t pos = 0UL; + + std::vector args; + bool on_args = false; + + for (auto& ch : line_after_define) { + ++pos; + + if (ch == '(') { + on_args = true; + continue; + } + + if (ch == ')') { + on_args = false; + continue; + } + + if (ch == '\\') continue; + + if (on_args) continue; + + if (ch == ' ') { + for (size_t i = pos; i < line_after_define.size(); i++) { + macro_value += line_after_define[i]; + } + + break; + } + + macro_key += ch; + } + + CompilerKit::STLString str; + + if (line_after_define.find("(") != CompilerKit::STLString::npos) { + line_after_define.erase(0, line_after_define.find("(") + 1); + + for (auto& subc : line_after_define) { + if (subc == ',' || subc == ')') { + if (str.empty()) continue; + + args.push_back(str); + + str.clear(); + + continue; + } + + str.push_back(subc); + } + } + + Detail::bpp_macro macro; + + macro.fArgs = args; + macro.fName = macro_key; + macro.fValue = macro_value; + + kMacros.emplace_back(macro); + + continue; + } + + if (hdr_line[0] != kMacroPrefix) { + if (inactive_code) { + continue; + } + + pp_out << hdr_line << std::endl; + + continue; + } + + if (hdr_line[0] == kMacroPrefix && hdr_line.find("ifndef") != CompilerKit::STLString::npos) { + auto line_after_ifndef = hdr_line.substr(hdr_line.find("ifndef") + strlen("ifndef") + 1); + CompilerKit::STLString macro; + + for (auto& ch : line_after_ifndef) { + if (ch == ' ') { + break; + } + + macro += ch; + } + + if (macro == "0") { + defined = true; + inactive_code = false; + continue; + } + + if (macro == "1") { + defined = false; + inactive_code = true; + + continue; + } + + bool found = false; + + defined = true; + inactive_code = false; + + for (auto& macro_ref : kMacros) { + if (hdr_line.find(macro_ref.fName) != CompilerKit::STLString::npos) { + found = true; + break; + } + } + + if (found) { + defined = false; + inactive_code = true; + + continue; + } + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("else") != CompilerKit::STLString::npos) { + if (!defined && inactive_code) { + inactive_code = false; + defined = true; + + continue; + } else { + defined = false; + inactive_code = true; + + continue; + } + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("ifdef") != CompilerKit::STLString::npos) { + auto line_after_ifdef = hdr_line.substr(hdr_line.find("ifdef") + strlen("ifdef") + 1); + CompilerKit::STLString macro; + + for (auto& ch : line_after_ifdef) { + if (ch == ' ') { + break; + } + + macro += ch; + } + + if (macro == "0") { + defined = false; + inactive_code = true; + + continue; + } + + if (macro == "1") { + defined = true; + inactive_code = false; + + continue; + } + + defined = false; + inactive_code = true; + + for (auto& macro_ref : kMacros) { + if (hdr_line.find(macro_ref.fName) != CompilerKit::STLString::npos) { + defined = true; + inactive_code = false; + + break; + } + } + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("if") != CompilerKit::STLString::npos) { + inactive_code = true; + + std::vector bpp_macro_condition_list = { + { + .fType = Detail::kEqual, + .fTypeName = "==", + }, + { + .fType = Detail::kNotEqual, + .fTypeName = "!=", + }, + { + .fType = Detail::kLesserThan, + .fTypeName = "<", + }, + { + .fType = Detail::kGreaterThan, + .fTypeName = ">", + }, + { + .fType = Detail::kLesserEqThan, + .fTypeName = "<=", + }, + { + .fType = Detail::kGreaterEqThan, + .fTypeName = ">=", + }, + }; + + int32_t good_to_go = 0; + + for (auto& macro_condition : bpp_macro_condition_list) { + if (hdr_line.find(macro_condition.fTypeName) != CompilerKit::STLString::npos) { + for (auto& found_macro : kMacros) { + if (hdr_line.find(found_macro.fName) != CompilerKit::STLString::npos) { + good_to_go = bpp_parse_if_condition(macro_condition, found_macro, inactive_code, + defined, hdr_line); + + break; + } + } + } + } + + if (good_to_go) continue; + + auto line_after_if = hdr_line.substr(hdr_line.find("if") + strlen("if") + 1); + CompilerKit::STLString macro; + + for (auto& ch : line_after_if) { + if (ch == ' ') { + break; + } + + macro += ch; + } + + if (macro == "0") { + defined = false; + inactive_code = true; + continue; + } + + if (macro == "1") { + defined = true; + inactive_code = false; + + continue; + } + + // last try, is it defined to be one? + for (auto& macro_ref : kMacros) { + if (macro_ref.fName.find(macro) != CompilerKit::STLString::npos && + macro_ref.fValue == "1") { + inactive_code = false; + defined = true; + + break; + } + } + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("warning") != CompilerKit::STLString::npos) { + auto line_after_warning = hdr_line.substr(hdr_line.find("warning") + strlen("warning") + 1); + CompilerKit::STLString message; + + for (auto& ch : line_after_warning) { + if (ch == '\r' || ch == '\n') { + break; + } + + message += ch; + } + + std::cout << "warn: " << message << std::endl; + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("error") != CompilerKit::STLString::npos) { + auto line_after_warning = hdr_line.substr(hdr_line.find("error") + strlen("error") + 1); + CompilerKit::STLString message; + + for (auto& ch : line_after_warning) { + if (ch == '\r' || ch == '\n') { + break; + } + + message += ch; + } + + throw std::runtime_error("error: " + message); + } else if (hdr_line[0] == kMacroPrefix && + hdr_line.find("include ") != CompilerKit::STLString::npos) { + line_after_include = hdr_line.substr(hdr_line.find("include ") + strlen("include ")); + + kIncludeFile: + auto it = std::find(kAllIncludes.cbegin(), kAllIncludes.cend(), line_after_include); + + if (it != kAllIncludes.cend()) { + continue; + } + + CompilerKit::STLString path; + + kAllIncludes.push_back(line_after_include); + + bool enable = false; + bool not_local = false; + + for (auto& ch : line_after_include) { + if (ch == ' ') continue; + + if (ch == '<') { + not_local = true; + enable = true; + + continue; + } + + if (ch == '\"') { + not_local = false; + enable = true; + continue; + } + + if (enable) { + path += ch; + } + } + + if (not_local) { + bool open = false; + + if (path.ends_with('>')) { + path.erase(path.find('>')); + } + + if (path.ends_with('"')) { + path.erase(path.find('"')); + } + + for (auto& include : kIncludes) { + CompilerKit::STLString header_path = include; + header_path.push_back('/'); + header_path += path; + + std::ifstream header(header_path); + + if (!header.is_open()) continue; + + open = true; + + bpp_parse_file(header, pp_out); + + break; + } + + if (!open) { + throw std::runtime_error("cppdrv: no such include file: " + path); + } + } else { + std::ifstream header(path); + + if (!header.is_open()) throw std::runtime_error("cppdrv: no such include file: " + path); + + bpp_parse_file(header, pp_out); + } + } else { + std::cerr << ("cppdrv: unknown pre-processor directive, " + hdr_line) << "\n"; + continue; + } + } + } catch (std::out_of_range& oor) { + return; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +// @brief main entrypoint of app. + +///////////////////////////////////////////////////////////////////////////////////////// + +NECTI_MODULE(CPlusPlusPreprocessorMain) { + try { + bool skip = false; + bool double_skip = false; + + Detail::bpp_macro macro_1; + + macro_1.fName = "__true"; + macro_1.fValue = "1"; + + kMacros.push_back(macro_1); + + Detail::bpp_macro macro_unreachable; + + macro_unreachable.fName = "__unreachable"; + macro_unreachable.fValue = "__compilerkit_unreachable"; + + kMacros.push_back(macro_unreachable); + + Detail::bpp_macro macro_unused; + + macro_unreachable.fName = "__unused"; + macro_unreachable.fValue = "__compilerkit_unused"; + + kMacros.push_back(macro_unused); + + Detail::bpp_macro macro_0; + + macro_0.fName = "__false"; + macro_0.fValue = "0"; + + kMacros.push_back(macro_0); + + Detail::bpp_macro macro_zka; + + macro_zka.fName = "__NECTI__"; + macro_zka.fValue = "1"; + + kMacros.push_back(macro_zka); + + Detail::bpp_macro macro_cxx; + + macro_cxx.fName = "__cplusplus"; + macro_cxx.fValue = "202302L"; + + kMacros.push_back(macro_cxx); + + Detail::bpp_macro macro_size_t; + macro_size_t.fName = "__SIZE_TYPE__"; + macro_size_t.fValue = "unsigned long long int"; + + kMacros.push_back(macro_size_t); + + macro_size_t.fName = "__UINT32_TYPE__"; + macro_size_t.fValue = "unsigned int"; + + kMacros.push_back(macro_size_t); + + macro_size_t.fName = "__UINTPTR_TYPE__"; + macro_size_t.fValue = "unsigned long long int"; + + kMacros.push_back(macro_size_t); + + for (auto index = 1UL; index < argc; ++index) { + if (skip) { + skip = false; + continue; + } + + if (double_skip) { + ++index; + double_skip = false; + continue; + } + + if (argv[index][0] == '-') { + if (strcmp(argv[index], "-cpp-ver") == 0) { + printf("%s\n", + "NeKernel Preprocessor Driver v1.11, (c) Amlal El Mahrouss 2024-2025 all rights " + "reserved."); + + return NECTI_SUCCESS; + } + + if (strcmp(argv[index], "-cpp-help") == 0) { + printf("%s\n", + "NeKernel Preprocessor Driver v1.11, (c) Amlal El Mahrouss 2024-2025 all rights " + "reserved."); + printf("%s\n", "-cpp-working-dir : set directory to working path."); + printf("%s\n", "-cpp-include-dir : add directory to include path."); + printf("%s\n", "-cpp-def : define a macro."); + printf("%s\n", "-cpp-ver: print the version."); + printf("%s\n", "-cpp-help: show help (this current command)."); + + return NECTI_SUCCESS; + } + + if (strcmp(argv[index], "-cpp-include-dir") == 0) { + CompilerKit::STLString inc = argv[index + 1]; + + skip = true; + + kIncludes.push_back(inc); + } + + if (strcmp(argv[index], "-cpp-working-dir") == 0) { + CompilerKit::STLString inc = argv[index + 1]; + skip = true; + kWorkingDir = inc; + } + + if (strcmp(argv[index], "-cpp-def") == 0 && argv[index + 1] != nullptr && + argv[index + 2] != nullptr) { + CompilerKit::STLString macro_key = argv[index + 1]; + + CompilerKit::STLString macro_value; + bool is_string = false; + + for (int argv_find_len = 0; argv_find_len < strlen(argv[index]); ++argv_find_len) { + if (!isdigit(argv[index][argv_find_len])) { + is_string = true; + macro_value += "\""; + + break; + } + } + + macro_value += argv[index + 2]; + + if (is_string) macro_value += "\""; + + Detail::bpp_macro macro; + macro.fName = macro_key; + macro.fValue = macro_value; + + kMacros.push_back(macro); + + double_skip = true; + } + + continue; + } + + kFiles.emplace_back(argv[index]); + } + + if (kFiles.empty()) return NECTI_EXEC_ERROR; + + for (auto& file : kFiles) { + if (!std::filesystem::exists(file)) continue; + + std::ifstream file_descriptor(file); + std::ofstream file_descriptor_pp(file + ".pp"); + + bpp_parse_file(file_descriptor, file_descriptor_pp); + } + + return NECTI_SUCCESS; + } catch (const std::runtime_error& e) { + std::cout << e.what() << '\n'; + } + + return NECTI_EXEC_ERROR; +} + +// Last rev 8-1-24 diff --git a/src/CompilerKit/src/StringKit.cc b/src/CompilerKit/src/StringKit.cc new file mode 100644 index 0000000..d612947 --- /dev/null +++ b/src/CompilerKit/src/StringKit.cc @@ -0,0 +1,179 @@ +/* + * ======================================================== + * + * CompilerKit + * Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + * + * ======================================================== + */ + +/** + * @file StringKit.cc + * @author Amlal (amlal@nekernel.org) + * @brief C++ string manipulation API. + * @version 0.2 + * @date 2024-01-23 + * + * @copyright Copyright (c) Amlal El Mahrouss + * + */ + +#include + +namespace CompilerKit { + +Char* BasicString::Data() { + return m_Data; +} + +const Char* BasicString::CData() const { + return m_Data; +} + +SizeType BasicString::Length() const { + return strlen(m_Data); +} + +bool BasicString::operator==(const BasicString& rhs) const { + const SizeType len = Length(); + if (rhs.Length() != len) return false; + return memcmp(m_Data, rhs.m_Data, len) == 0; +} + +bool BasicString::operator==(const Char* rhs) const { + const SizeType rhs_len = string_length(rhs); + const SizeType len = Length(); + if (rhs_len != len) return false; + return memcmp(m_Data, rhs, len) == 0; +} + +bool BasicString::operator!=(const BasicString& rhs) const { + return !(*this == rhs); +} + +bool BasicString::operator!=(const Char* rhs) const { + return !(*this == rhs); +} + +BasicString StringBuilder::Construct(const Char* data) { + if (!data || *data == 0) return BasicString(0); + + BasicString view(strlen(data)); + view += data; + + return view; +} + +BasicString StringBuilder::FromInt(const char* fmt, int i) { + if (!fmt) return BasicString(0); + + Char result[sizeof(int64_t)] = {0}; + if (!to_str(result, sizeof(int64_t), i)) return BasicString(0); + + const SizeType fmt_len = string_length(fmt); + const SizeType res_len = string_length(result); + + BasicString output(fmt_len + res_len); + bool inserted = false; + + for (SizeType idx = 0; idx < fmt_len; ++idx) { + if (!inserted && fmt[idx] == '%') { + output += result; + inserted = true; + continue; + } + output += Char{fmt[idx]}; + } + + return output; +} + +BasicString StringBuilder::FromBool(const char* fmt, bool val) { + if (!fmt) return BasicString(0); + + const Char* boolean_expr = val ? "true" : "false"; + const SizeType fmt_len = string_length(fmt); + const SizeType res_len = string_length(boolean_expr); + + BasicString output(fmt_len + res_len); + bool inserted = false; + + for (SizeType idx = 0; idx < fmt_len; ++idx) { + if (!inserted && fmt[idx] == '%') { + output += boolean_expr; + inserted = true; + continue; + } + output += Char{fmt[idx]}; + } + + return output; +} + +bool StringBuilder::Equals(const char* lhs, const char* rhs) { + const SizeType lhs_len = string_length(lhs); + const SizeType rhs_len = string_length(rhs); + + if (lhs_len != rhs_len) return false; + return memcmp(lhs, rhs, lhs_len) == 0; +} + +BasicString StringBuilder::Format(const char* fmt, const char* fmtRight) { + if (!fmt || !fmtRight) return BasicString(0); + + const SizeType fmt_len = string_length(fmt); + const SizeType rhs_len = string_length(fmtRight); + + BasicString output(fmt_len + rhs_len); + bool inserted = false; + + for (SizeType idx = 0; idx < fmt_len; ++idx) { + if (!inserted && fmt[idx] == '%') { + output += fmtRight; + inserted = true; + continue; + } + output += Char{fmt[idx]}; + } + + return output; +} + +BasicString& BasicString::operator+=(const Char* rhs) { + const SizeType rhs_len = strlen(rhs); + if (this->m_Cur + rhs_len >= this->m_Sz) { + throw std::runtime_error("out_of_bounds: BasicString"); + } + + memcpy(this->m_Data + this->m_Cur, rhs, rhs_len); + + this->m_Cur += rhs_len; + this->m_Data[this->m_Cur] = '\0'; + + return *this; +} + +BasicString& BasicString::operator+=(const BasicString& rhs) { + if (this->m_Cur + rhs.m_Cur >= this->m_Sz) { + throw std::runtime_error("out_of_bounds: BasicString"); + } + + memcpy(this->m_Data + this->m_Cur, rhs.CData(), rhs.m_Cur); + this->m_Cur += rhs.m_Cur; + this->m_Data[this->m_Cur] = '\0'; + + return *this; +} + +BasicString& BasicString::operator+=(const Char ch) { + if (this->m_Cur + 1 >= this->m_Sz) { + throw std::runtime_error("out_of_bounds.."); + } + + this->m_Data[this->m_Cur++] = ch; + this->m_Data[this->m_Cur] = '\0'; + + return *this; +} + +} // namespace CompilerKit diff --git a/src/CompilerKit/utils/AsmUtils.h b/src/CompilerKit/utils/AsmUtils.h new file mode 100644 index 0000000..897fcbe --- /dev/null +++ b/src/CompilerKit/utils/AsmUtils.h @@ -0,0 +1,92 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include + +using namespace CompilerKit; + +/// @brief Get Number from lineBuffer. +/// @param lineBuffer the lineBuffer to fetch from. +/// @param numberKey where to seek that number. +/// @return +static NumberCast32 GetNumber32(STLString lineBuffer, STLString numberKey) { + auto pos = lineBuffer.find(numberKey) + numberKey.size(); + + while (lineBuffer[pos] == ' ') { + ++pos; + } + + switch (lineBuffer[pos + 1]) { + case 'x': { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 16)); + + if (kVerbose) { + kStdOut << "asm: found a base 16 number here: " << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + case 'b': { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number:" + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "asm: found a base 2 number here:" << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + case 'o': { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "asm: found a base 8 number here:" << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + default: { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 10)); + + if (kVerbose) { + kStdOut << "asm: found a base 10 number here:" << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + } +} diff --git a/src/CompilerKit/utils/CompilerUtils.h b/src/CompilerKit/utils/CompilerUtils.h new file mode 100644 index 0000000..1b086ee --- /dev/null +++ b/src/CompilerKit/utils/CompilerUtils.h @@ -0,0 +1,116 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#define kZero64Section ".zero64" +#define kCode64Section ".code64" +#define kData64Section ".data64" + +#define kZero128Section ".zero128" +#define kCode128Section ".code128" +#define kData128Section ".data128" + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" +#define kYellow "\e[0;33m" + +#define kStdOut (std::cout << kRed << "drv: " << kWhite) +#define kStdErr (std::cout << kYellow << "drv: " << kWhite) + +inline static UInt32 kErrorLimit = 10; +inline static UInt32 kAcceptableErrors = 0; +inline static bool kVerbose = false; +inline static bool kOutputAsBinary = false; + +namespace Detail { +/// @brief Linker specific blob metadata structure +struct DynamicLinkerBlob final { + std::vector mBlob{}; // PEF code/bss/data blob. + UIntPtr mOffset{0UL}; // the offset of the PEF container header... +}; + +inline void print_error(std::string reason, std::string file) noexcept { + if (reason[0] == '\n') reason.erase(0, 1); + + kStdErr << reason << kBlank << std::endl; + + if (kAcceptableErrors > kErrorLimit) std::exit(NECTI_EXEC_ERROR); + + ++kAcceptableErrors; +} + +inline void print_warning(std::string reason, std::string file) noexcept { + if (reason[0] == '\n') reason.erase(0, 1); + + kStdOut << kYellow << reason << kBlank << std::endl; +} + +/// @internal +/// @brief Handler for SIGSEGV signal. +inline void drvi_crash_handler(std::int32_t id) { + CompilerKit::STLString verbose_header = "LIBCOMPILER CRASH REPORT - "; + verbose_header += kDistVersion; + verbose_header += " - "; + verbose_header += CompilerKit::current_date(); + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + std::cout << verbose_header << std::endl; + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + kStdOut << "DATE: " << CompilerKit::current_date() << std::endl; + kStdOut << "VERSION: " << kDistVersion << std::endl; + kStdOut << "ERRNO: " << errno << std::endl; + kStdOut << "ERRNO(STRING): " << strerror(errno) << std::endl; + + switch (id) { + case SIGSEGV: { + kStdOut << "SIGNAL: Segmentation Fault." << kBlank << std::endl; + break; + } + case SIGABRT: { + kStdOut << "SIGNAL: Aborted." << kBlank << std::endl; + break; + } + } + + std::cout << kWhite; + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + std::cout << verbose_header << std::endl; + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + std::exit(NECTI_EXEC_ERROR); +} +} // namespace Detail diff --git a/src/CompilerKit/utils/DylibHelpers.h b/src/CompilerKit/utils/DylibHelpers.h new file mode 100644 index 0000000..ab58fc7 --- /dev/null +++ b/src/CompilerKit/utils/DylibHelpers.h @@ -0,0 +1,63 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include + +struct CompilerKitDylibTraits; + +typedef Int32 (*CompilerKitEntrypoint)(Int32 argc, Char const* argv[]); +typedef VoidPtr CompilerKitDylib; + +struct CompilerKitDylibTraits final { + CompilerKitDylib fDylib{nullptr}; + CompilerKitEntrypoint fEntrypoint{nullptr}; + std::mutex fMutex; + + CompilerKitDylibTraits& operator()(const Char* path, const Char* fEntrypoint) { + std::lock_guard lock(this->fMutex); + + if (!path || !fEntrypoint) return *this; + + if (this->fDylib) { + dlclose(this->fDylib); + this->fDylib = nullptr; + } + + this->fDylib = dlopen(path, RTLD_LAZY); + + if (!this->fDylib) { + return *this; + } + + this->fEntrypoint = (CompilerKitEntrypoint) dlsym(this->fDylib, fEntrypoint); + + if (!this->fEntrypoint) { + dlclose(this->fDylib); + this->fDylib = nullptr; + + return *this; + } + + return *this; + } + + NECTI_COPY_DELETE(CompilerKitDylibTraits); + + CompilerKitDylibTraits() = default; + + ~CompilerKitDylibTraits() { + if (this->fDylib) { + dlclose(this->fDylib); + this->fDylib = nullptr; + } + + this->fEntrypoint = nullptr; + } +}; diff --git a/src/DebuggerKit/CommonCLI.inl b/src/DebuggerKit/CommonCLI.inl new file mode 100644 index 0000000..e06a9b9 --- /dev/null +++ b/src/DebuggerKit/CommonCLI.inl @@ -0,0 +1,23 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" + +#define kStdOut (std::cout << kRed << "dbg: " << kWhite) + +static Bool kKeepRunning = false; + +#ifdef DK_NEKERNEL_DEBUGGER +static DebuggerKit::NeKernel::NeKernelContract kKernelDebugger; +#else +static DebuggerKit::POSIX::POSIXMachContract kUserDebugger; +#endif + +static DebuggerKit::ProcessID kPID = 0L; +static DebuggerKit::CAddress kActiveAddress = nullptr; +static CompilerKit::STLString kPath = ""; diff --git a/src/DebuggerKit/DebuggerContract.h b/src/DebuggerKit/DebuggerContract.h new file mode 100644 index 0000000..2114041 --- /dev/null +++ b/src/DebuggerKit/DebuggerContract.h @@ -0,0 +1,52 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include + +#define DK_DEBUGGER_CONTRACT : public ::DebuggerKit::DebuggerContract + +namespace DebuggerKit { +class DebuggerContract; + +/// =========================================================== /// +/// \brief Process ID +/// =========================================================== /// +typedef uint64_t ProcessID; + +/// =========================================================== /// +/// \brief Address type, a la BSD. +/// =========================================================== /// +typedef char* CAddress; + +/// =========================================================== /// +/// \brief Debugger contract class in C++, as per the design states. +/// \author Amlal El Mahrouss +/// =========================================================== /// +class DebuggerContract { + public: + explicit DebuggerContract() = default; + virtual ~DebuggerContract() = default; + + public: + DebuggerContract& operator=(const DebuggerContract&) = default; + DebuggerContract(const DebuggerContract&) = default; + + public: + virtual bool Attach(std::string path, std::string argv, ProcessID& pid) noexcept = 0; + virtual bool BreakAt(std::string symbol) noexcept = 0; + virtual bool Break() noexcept = 0; + virtual bool Continue() noexcept = 0; + virtual bool Detach() noexcept = 0; + + virtual std::unordered_map& Get() { return m_breakpoints; } + + protected: + ProcessID m_pid{(ProcessID)~0}; + std::unordered_map m_breakpoints; +}; +} // namespace DebuggerKit diff --git a/src/DebuggerKit/NeKernelContract.h b/src/DebuggerKit/NeKernelContract.h new file mode 100644 index 0000000..d048303 --- /dev/null +++ b/src/DebuggerKit/NeKernelContract.h @@ -0,0 +1,60 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifndef DK_NEKERNEL_CONTRACT_H +#define DK_NEKERNEL_CONTRACT_H + +/// @brief NeKernel Debugging Protocol +/// @author Amlal El Mahrouss + +#ifdef DK_NEKERNEL_DEBUGGER + +#include +#include + +namespace DebuggerKit::NeKernel { +class NeKernelContract; + +namespace Detail { + inline constexpr auto kDebugCmdLen = 256U; + inline constexpr auto kDebugPort = 51820; + inline constexpr auto kDebugMagic = "NE1.0.0;"; + inline constexpr uint16_t kDebugVersion = 0x0100; + inline constexpr auto kDebugDelim = ';'; + inline constexpr auto kDebugEnd = '\r'; + typedef int64_t dk_socket_type; +} // namespace Detail + +/// =========================================================== /// +/// \brief NeKernel Debugger Contract +/// \author Amlal El Mahrouss +/// =========================================================== /// +class NeKernelContract final DK_DEBUGGER_CONTRACT { + public: + NeKernelContract(); + virtual ~NeKernelContract() override; + + public: + NeKernelContract& operator=(const NeKernelContract&) = default; + NeKernelContract(const NeKernelContract&) = default; + + public: + bool Attach(CompilerKit::STLString path, CompilerKit::STLString arg_v, + ProcessID& pid) noexcept override; + bool BreakAt(CompilerKit::STLString symbol) noexcept override; + bool Break() noexcept override; + bool Continue() noexcept override; + bool Detach() noexcept override; + + private: + CompilerKit::STLString m_kernel_path{}; + Detail::dk_socket_type m_socket{0}; +}; +} // namespace DebuggerKit::NeKernel + +#endif // ifdef DK_NEKERNEL_DEBUGGER + +#endif // DK_NEKERNEL_CONTRACT_H diff --git a/src/DebuggerKit/POSIXMachContract.h b/src/DebuggerKit/POSIXMachContract.h new file mode 100644 index 0000000..5bd4ba8 --- /dev/null +++ b/src/DebuggerKit/POSIXMachContract.h @@ -0,0 +1,147 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#ifdef DK_MACH_DEBUGGER + +/// @file POSIXMachContract.h +/// @brief POSIX Mach debugger. + +#include + +CK_IMPORT_C kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, + vm_offset_t data, mach_msg_type_number_t dataCnt); + +CK_IMPORT_C kern_return_t mach_vm_protect(vm_map_t target_task, mach_vm_address_t address, + mach_vm_size_t size, boolean_t set_maximum, + vm_prot_t new_protection); + +#define PTRACE_ATTACH PT_ATTACHEXC +#define PTRACE_DETACH PT_DETACH +#define PTRACE_POKETEXT PT_WRITE_I +#define PTRACE_CONT PT_CONTINUE +#define PTRACE_PEEKTEXT PT_READ_I + +namespace DebuggerKit::POSIX { +/// =========================================================== /// +/// \brief POSIXMachContract engine class in C++ +/// \author Amlal El Mahrouss +/// =========================================================== /// +class POSIXMachContract final DK_DEBUGGER_CONTRACT { + public: + explicit POSIXMachContract() = default; + ~POSIXMachContract() override = default; + + public: + POSIXMachContract& operator=(const POSIXMachContract&) = default; + POSIXMachContract(const POSIXMachContract&) = default; + + public: + Bool Attach(CompilerKit::STLString path, CompilerKit::STLString argv, + ProcessID& pid) noexcept override { + pid = fork(); + + if (pid == 0) { + if (argv.empty()) { + ptrace(PT_TRACE_ME, 0, nullptr, 0); + kill(getpid(), SIGSTOP); + } + + std::vector argv_arr; + + argv_arr.push_back(const_cast(path.c_str())); + argv_arr.push_back(const_cast(argv.c_str())); + argv_arr.push_back(nullptr); + + execv(path.c_str(), argv_arr.data()); + + _exit(1); + } + + m_path = path; + m_pid = pid; + + pid = this->m_pid; + + return true; + } + + void SetPath(CompilerKit::STLString path) noexcept { + if (path.empty()) { + return; + } + + m_path = path; + } + + Bool BreakAt(CompilerKit::STLString symbol) noexcept override { + if (!m_path.empty() && std::filesystem::exists(m_path) && + std::filesystem::is_regular_file(m_path)) { + auto handle = dlopen(m_path.c_str(), RTLD_LAZY); + + if (handle == nullptr) { + return false; + } + + auto addr = dlsym(handle, symbol.c_str()); + + if (addr == nullptr) { + return false; + } + + task_read_t task; + task_for_pid(mach_task_self(), m_pid, &task); + + uint32_t brk_inst = 0xD43E0000; + + mach_vm_protect(task, (mach_vm_address_t) addr, sizeof(uint32_t), false, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE); + + mach_vm_write(task, (mach_vm_address_t) addr, (vm_offset_t) &brk_inst, sizeof(addr)); + + return true; + } + + return false; + } + + Bool Break() noexcept override { + task_read_t task; + task_for_pid(mach_task_self(), m_pid, &task); + + kern_return_t ret = task_suspend(task); + + return ret == KERN_SUCCESS; + } + + Bool Continue() noexcept override { + task_read_t task; + task_for_pid(mach_task_self(), m_pid, &task); + + kern_return_t ret = task_resume(task); + + return ret == KERN_SUCCESS; + } + + Bool Detach() noexcept override { + this->Continue(); + + task_read_t task; + task_for_pid(mach_task_self(), m_pid, &task); + + kern_return_t kr = mach_port_deallocate(mach_task_self(), task); + + return kr = KERN_SUCCESS; + } + + private: + ProcessID m_pid{0}; + CompilerKit::STLString m_path; +}; +} // namespace DebuggerKit::POSIX + +#endif diff --git a/src/DebuggerKit/Platform.h b/src/DebuggerKit/Platform.h new file mode 100644 index 0000000..f878845 --- /dev/null +++ b/src/DebuggerKit/Platform.h @@ -0,0 +1,37 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +/// =========================================================== /// +/// @author Amlal El Mahrouss +/// =========================================================== /// + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include \ No newline at end of file diff --git a/src/DebuggerKit/Version.h b/src/DebuggerKit/Version.h new file mode 100644 index 0000000..8f3168a --- /dev/null +++ b/src/DebuggerKit/Version.h @@ -0,0 +1,15 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#define kDistVersion "v0.0.7-debuggerkit" +#define kDistVersionBCD 0x0001 + +#define ToString(X) Stringify(X) +#define Stringify(X) #X + +#define kDistRelease ToString(kDistReleaseBranch) diff --git a/src/DebuggerKit/dk-nekernel.json b/src/DebuggerKit/dk-nekernel.json new file mode 100644 index 0000000..9c3d311 --- /dev/null +++ b/src/DebuggerKit/dk-nekernel.json @@ -0,0 +1,17 @@ +{ + "compiler_path": "clang++", + "compiler_std": "c++20", + "headers_path": [ + "../DebuggerKit", + "../" + ], + "sources_path": ["src/*.cc"], + "output_name": "/usr/local/lib/libDebuggerKit.dylib", + "compiler_flags": ["-fPIC", "-shared"], + "cpp_macros": [ + "__NECTI__=202505", + "DK_USE_STRUCTS=1", + "DK_NEKERNEL_DEBUGGER", + "kDistReleaseBranch=$(git rev-parse --abbrev-ref HEAD)-$(uuidgen)" + ] +} diff --git a/src/DebuggerKit/dk-osx.json b/src/DebuggerKit/dk-osx.json new file mode 100644 index 0000000..c220756 --- /dev/null +++ b/src/DebuggerKit/dk-osx.json @@ -0,0 +1,18 @@ +{ + "compiler_path": "clang++", + "compiler_std": "c++20", + "headers_path": [ + "../DebuggerKit", + "../" + ], + "sources_path": ["src/*.cc"], + "output_name": "/usr/local/lib/libDebuggerKit.dylib", + "compiler_flags": ["-fPIC", "-shared"], + "cpp_macros": [ + "__NECTI__=202505", + "CK_USE_STRUCTS=1", + "DEBUGGERKIT_POSIX", + "DK_MACH_DEBUGGER", + "kDistReleaseBranch=$(git rev-parse --abbrev-ref HEAD)-$(uuidgen)" + ] +} diff --git a/src/DebuggerKit/src/NeKernelContract.cc b/src/DebuggerKit/src/NeKernelContract.cc new file mode 100644 index 0000000..38f327d --- /dev/null +++ b/src/DebuggerKit/src/NeKernelContract.cc @@ -0,0 +1,90 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifdef DK_NEKERNEL_DEBUGGER + +/// @author Amlal El Mahrouss +/// @brief Kernel Debugger Protocol + +#include +#include +#include +#include + +using namespace DebuggerKit::NeKernel; +using namespace DebuggerKit::NeKernel::Detail; + +NeKernelContract::NeKernelContract() = default; + +NeKernelContract::~NeKernelContract() = default; + +Bool NeKernelContract::Attach(CompilerKit::STLString path, CompilerKit::STLString argv, + ProcessID& pid) noexcept { + if (path.empty() || argv.empty()) return NO; + + m_socket = ::socket(AF_INET, SOCK_STREAM, 0); + + if (m_socket == -1) return NO; + + struct sockaddr_in server_addr; + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(kDebugPort); + + if (::inet_pton(AF_INET, argv.c_str(), &server_addr.sin_addr) <= 0) return NO; + + auto ret = (::connect(m_socket, (struct sockaddr*) &server_addr, sizeof(server_addr)) == -1); + + if (ret) return NO; + + CompilerKit::STLString pkt = Detail::kDebugMagic; + pkt += ";\r"; + + ret = ::send(m_socket, pkt.data(), pkt.size(), 0) > 0; + return ret; +} + +Bool NeKernelContract::BreakAt(CompilerKit::STLString symbol) noexcept { + CompilerKit::STLString pkt = Detail::kDebugMagic; + pkt += ";SYM="; + pkt += symbol; + pkt += ";\r"; + + if (pkt.size() > kDebugCmdLen) return NO; + + auto ret = ::send(m_socket, pkt.data(), pkt.size(), 0) > 0; + return ret; +} + +Bool NeKernelContract::Break() noexcept { + CompilerKit::STLString pkt = Detail::kDebugMagic; + pkt += ";BRK=1;\r"; + + auto ret = ::send(m_socket, pkt.data(), pkt.size(), 0) > 0; + return ret; +} + +Bool NeKernelContract::Continue() noexcept { + CompilerKit::STLString pkt = Detail::kDebugMagic; + pkt += ";CONT=1;\r"; + + auto ret = ::send(m_socket, pkt.data(), pkt.size(), 0) > 0; + return ret; + return NO; +} + +Bool NeKernelContract::Detach() noexcept { + CompilerKit::STLString pkt = Detail::kDebugMagic; + pkt += ";DTCH=1;\r"; + + auto ret = ::send(m_socket, pkt.data(), pkt.size(), 0) > 0; + + if (ret) ::close(m_socket); + + return ret; +} + +#endif // DK_NEKERNEL_DEBUGGER diff --git a/src/DebuggerKit/src/NeKernelContractCLI.cc b/src/DebuggerKit/src/NeKernelContractCLI.cc new file mode 100644 index 0000000..fc13a53 --- /dev/null +++ b/src/DebuggerKit/src/NeKernelContractCLI.cc @@ -0,0 +1,100 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifdef DK_NEKERNEL_DEBUGGER + +#include +#include +#include +#include + +#include + +using namespace DebuggerKit::NeKernel; + +static void dbgi_ctrlc_handler(std::int32_t _) { + if (!kPID || kPath.empty()) { + return; + } + + kKernelDebugger.Break(); + + pfd::notify("Debugger Event", "Breakpoint hit!"); + + kKeepRunning = false; +} + +NECTI_MODULE(DebuggerNeKernel) { + pfd::notify("Debugger Event", + "NeCTI Debugger\n(C) 2025 Amlal El Mahrouss and NeKernel.org contributors, all " + "rights reserved."); + + if (argc >= 5 && std::string(argv[1]) == "-k" && argv[2] != nullptr && + std::string(argv[3]) == "-ip" && argv[4] != nullptr) { + kPath = argv[2]; + kPath += ":"; + kPath += argv[4]; + + kStdOut << "[+] KIP (Kernel:IP) set to: " << kPath << "\n"; + + CompilerKit::install_signal(SIGINT, dbgi_ctrlc_handler); + + kKernelDebugger.Attach(kPath, argv[4], kPID); + + while (YES) { + if (kKeepRunning) { + continue; + } + + std::string cmd; + if (!std::getline(std::cin, cmd)) break; + + if (cmd == "c" || cmd == "cont" || cmd == "continue") { + if (kKernelDebugger.Continue()) { + kKeepRunning = true; + + kStdOut << "[+] Continuing...\n"; + + pfd::notify("Debugger Event", "Continuing..."); + } + } + + if (cmd == "d" || cmd == "detach") kKernelDebugger.Detach(); + + if (cmd == "start") { + kStdOut << "[?] Enter a argument to use: "; + std::getline(std::cin, cmd); + + kKernelDebugger.Attach(kPath, cmd, kPID); + } + + if (cmd == "exit") { + if (kPID > 0) kKernelDebugger.Detach(); + + break; + } + + if (cmd == "break" || cmd == "b") { + kStdOut << "[?] Enter a symbol to break on: "; + + std::getline(std::cin, cmd); + + if (kKernelDebugger.BreakAt(cmd)) { + pfd::notify("Debugger Event", "Add BreakAt at: " + cmd); + } + } + } + + return EXIT_SUCCESS; + } + + kStdOut << "usage: " << argv[0] << " -k -ip \n"; + kStdOut << "example: " << argv[0] << " -k /path/to/ne_kernel -ip 127.0.0.1\n"; + + return EXIT_FAILURE; +} + +#endif // DK_NEKERNEL_DEBUGGER diff --git a/src/DebuggerKit/src/POSIXMachContractCLI.cc b/src/DebuggerKit/src/POSIXMachContractCLI.cc new file mode 100644 index 0000000..77fe844 --- /dev/null +++ b/src/DebuggerKit/src/POSIXMachContractCLI.cc @@ -0,0 +1,94 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifdef DK_MACH_DEBUGGER + +#include +#include +#include +#include + +/// @internal +/// @brief Handles CTRL-C signal on debugger. +static void dbgi_ctrlc_handler(std::int32_t _) { + if (!kPID) { + return; + } + + kUserDebugger.Break(); + + pfd::notify("Debugger Event", "Breakpoint hit!"); + + kKeepRunning = false; +} + +NECTI_MODULE(DebuggerMachPOSIX) { + pfd::notify( + "Debugger Event", + "Userland Debugger\n(C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license."); + + if (argc >= 3 && std::string(argv[1]) == "-p" && argv[2] != nullptr) { + kPath = argv[2]; + kUserDebugger.SetPath(kPath); + + kStdOut << "[+] Image set to: " << kPath << "\n"; + } else { + kStdOut << "usage: " << argv[0] << " -p \n"; + kStdOut << "example: " << argv[0] << " -p /path/to/program\n"; + + return EXIT_FAILURE; + } + + CompilerKit::install_signal(SIGINT, dbgi_ctrlc_handler); + + while (YES) { + if (kKeepRunning) { + continue; + } + + std::string cmd; + if (!std::getline(std::cin, cmd)) break; + + if (cmd == "c" || cmd == "cont" || cmd == "continue") { + if (kUserDebugger.Continue()) { + kKeepRunning = true; + + kStdOut << "[+] Continuing...\n"; + + pfd::notify("Debugger Event", "Continuing..."); + } + } + + if (cmd == "d" || cmd == "detach") kUserDebugger.Detach(); + + if (cmd == "start") { + kStdOut << "[?] Enter a argument to use: "; + std::getline(std::cin, cmd); + + kUserDebugger.Attach(kPath, cmd, kPID); + } + + if (cmd == "exit") { + if (kPID > 0) kUserDebugger.Detach(); + + break; + } + + if (cmd == "break" || cmd == "b") { + kStdOut << "[?] Enter a symbol to break on: "; + + std::getline(std::cin, cmd); + + if (kUserDebugger.BreakAt(cmd)) { + pfd::notify("Debugger Event", "Add BreakAt at: " + cmd); + } + } + } + + return EXIT_SUCCESS; +} + +#endif \ No newline at end of file diff --git a/src/LibC++/.gitignore b/src/LibC++/.gitignore new file mode 100644 index 0000000..5fa1170 --- /dev/null +++ b/src/LibC++/.gitignore @@ -0,0 +1 @@ +stdcxx/ diff --git a/src/LibC++/__abi+unreachable.cc b/src/LibC++/__abi+unreachable.cc new file mode 100644 index 0000000..1fc9830 --- /dev/null +++ b/src/LibC++/__abi+unreachable.cc @@ -0,0 +1,17 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#include +#include + +static const int32_t __unreachable_code = 34; + +extern "C" void __compilerkit_unreachable(void) { + std::base_process::signal(__unreachable_code); + + while (1) + ; +} \ No newline at end of file diff --git a/src/LibC++/__abi.h b/src/LibC++/__abi.h new file mode 100644 index 0000000..70afae5 --- /dev/null +++ b/src/LibC++/__abi.h @@ -0,0 +1,15 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include + +__init_decl() + +extern void __compilerkit_unreachable(void); + +__fini_decl() \ No newline at end of file diff --git a/src/LibC++/__power64.inc b/src/LibC++/__power64.inc new file mode 100644 index 0000000..c06863a --- /dev/null +++ b/src/LibC++/__power64.inc @@ -0,0 +1,39 @@ +# Path: LibC++/__power64.inc +# Language: CompilerKit POWER Assembly support for GNU. +# Build Date: 2024-6-4 + +#ifdef __NECTI__ + +#ifdef __ASSEMBLER__ + +#define lda li +#define sta stw +#define ldw li + +#define r0 0 +#define r1 1 +#define r2 2 +#define r3 3 +#define r4 4 +#define r5 5 +#define r6 6 +#define r7 7 +#define r8 8 +#define r9 9 +#define r10 10 +#define r11 11 +#define r12 12 +#define r13 13 +#define r14 14 +#define r15 15 +#define r16 16 +#define r17 17 +#define r18 18 +#define r19 19 +#define r20 20 + +#define nop mr 0, 0 + +#endif + +#endif diff --git a/src/LibC++/base_alloc.h b/src/LibC++/base_alloc.h new file mode 100644 index 0000000..ea5b5b2 --- /dev/null +++ b/src/LibC++/base_alloc.h @@ -0,0 +1,43 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include + +namespace std::base_alloc { +/// @brief allocate a new class. +/// @tparam KindClass the class type to allocate. +template +inline KindClass* allocate(Args&&... args) { + return new KindClass(forward(args)...); +} + +/// @brief allocate a new class. +/// @note aborts on error. +/// @tparam KindClass the class type to allocate. +template +inline KindClass* allocate_nothrow(Args&&... args) noexcept { + return allocate(forward(args)...); +} + +/// @brief free a class. +/// @tparam KindClass the class type to allocate. +template +inline void release(KindClass ptr) { + if (!ptr) return; + + delete ptr; +} + +/// @brief destroy and free a class. +/// @note aborts on error. +/// @tparam KindClass the class type to allocate. +template +inline void release_nothrow(KindClass ptr) noexcept { + release(ptr); +} +} // namespace std::base_alloc diff --git a/src/LibC++/base_exception.h b/src/LibC++/base_exception.h new file mode 100644 index 0000000..8747688 --- /dev/null +++ b/src/LibC++/base_exception.h @@ -0,0 +1,37 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include +#include +#include +#include + +/// @author Amlal El Mahrouss (amlal@nekernel.org) + +namespace std::base_exception::abi { +inline constexpr int __terminate_id = 33; + +/// @note This function is internal, don't call it. +extern void __unwind_object_list(); + +inline void __throw_general(const char* what) { + std::cout << "LibC++: Unwinding exception of kind: " << what << ", aborting here..." << std::endl; + __unwind_object_list(); + base_process::exit(__terminate_id); +} + +inline void __throw_domain_error(const char* what) { + __throw_general(what); + __builtin_unreachable(); // prevent from continuing. +} + +inline void __throw_bad_array_new_length(const char* what) { + __throw_general(what); + __builtin_unreachable(); // prevent from continuing. +} +} // namespace std::base_exception::abi diff --git a/src/LibC++/base_math.h b/src/LibC++/base_math.h new file mode 100644 index 0000000..60b260e --- /dev/null +++ b/src/LibC++/base_math.h @@ -0,0 +1,88 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include + +#ifndef NAN +#define NAN (__builtin_nanf("")) +#endif // !NAN + +/// @file Math.h +/// @brief Math functions. + +#ifdef __LIBCXX_USE_DOUBLE__ +typedef double real_type; +#else +typedef float real_type; +#endif + +namespace std::base_math { +inline constexpr static auto not_a_number = NAN; + +/// =========================================================== /// +/// @brief Power function, with Repeat argument. +/// =========================================================== /// +template +inline real_type pow(real_type in) { + if (Exponent == 0) return 1; // Any number to the power of 0 is 1. + + if (Exponent == 1) return in; // Any number to the power of 1 is itself. + + size_t cnt = Exponent; + + real_type result = 1; + + for (auto i = 0; i < cnt; ++i) result *= in; + + return result; +} + +/// =========================================================== /// +/// @brief Square root function. +/// =========================================================== /// +inline real_type sqrt(real_type in) { + if (in == 0) return 0; + if (in == not_a_number) return not_a_number; + + auto constexpr const static Base = 2; + + auto x = in / Base; + + for (int i = 0; i < 10; ++i) { + x = (x + in / x) / Base; + } + + return x; +} + +/// =========================================================== /// +/// @brief Square of function, with Base template argument. +/// @param of Base argument to find the square of. +/// =========================================================== /// +template +inline real_type surd(real_type in) { + if (in == 0) return 0; + if (in == 1) return 1; + + if (Base == 1) return in; + if (Base == 2) return sqrt(in); + + return not_a_number; +} + +/// =========================================================== /// +/// @brief Linear interpolation equation solver. +/// @param from where? +/// @param to to? +/// @param Updated diff value according to difference. +/// =========================================================== /// +inline real_type lerp(real_type to, real_type from, real_type stat) { + real_type diff = (to - from); + return from + (diff * stat); +} +} // namespace std::base_math diff --git a/src/LibC++/base_process.h b/src/LibC++/base_process.h new file mode 100644 index 0000000..f9b0596 --- /dev/null +++ b/src/LibC++/base_process.h @@ -0,0 +1,45 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include + +__init_decl() + +/// @brief CRT exit, with exit code (!!! exits all threads. !!!) +/// @param code the exit code. +/// @return the return > 0 for non successful. +extern int exit_(int code); + +/// @brief CRT signal handler. +/// @param code the signal code. +extern void signal_(int code); + +extern void (*__atexit_cdecl_ptr)(void); +extern void (**__atexit_lst_ptr)(void); +extern size_t __atexit_lst_cnt; + +__fini_decl() + +/// @brief Standard C++ namespace +namespace std::base_process { +inline int signal(int code) { + signal_(code); + return -1; +} + +inline int32_t exit(const int32_t& code) { + for (auto idx = 0UL; idx < __atexit_lst_cnt; ++idx) { + __atexit_lst_ptr[idx](); + } + + if (__atexit_cdecl_ptr) __atexit_cdecl_ptr(); + + exit_(code); + return -1; +} +} // namespace std::base_process diff --git a/src/LibC++/defines.h b/src/LibC++/defines.h new file mode 100644 index 0000000..b8ef10c --- /dev/null +++ b/src/LibC++/defines.h @@ -0,0 +1,76 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifndef __NECTI_DEFINES_H__ +#define __NECTI_DEFINES_H__ + +#define __ATTRIBUTE(X) __attribute__((X)) + +typedef __SIZE_TYPE__ size_t; +typedef __INT64_TYPE__ ssize_t; +typedef __INT32_TYPE__ int32_t; + +typedef void* ptr_type; +typedef __SIZE_TYPE__ size_type; + +typedef __INT64_TYPE__ ptrdiff_t; +typedef size_t uintptr_t; +typedef void* voidptr_t; +typedef void* any_t; +typedef char* caddr_t; + +#ifndef NULL +#define NULL ((voidptr_t) 0) +#endif // !null + +#define __alloca(sz) __ck_alloca(sz) + +#define __deref(ptr) (*(ptr)) + +#ifdef __cplusplus +#define __init_decl() extern "C" { +#define __fini_decl() \ + } \ + ; +#else +#define __init_decl() +#define __fini_decl() +#endif + +#if __has_builtin(__builtin_alloca) +#define alloca(sz) __builtin_alloca(sz) +#ifdef __alloca +#undef __alloca +#endif +#define __alloca alloca +#else +#warning !! alloca not detected !! +#endif + +typedef long long off_t; +typedef unsigned long long uoff_t; + +typedef union float_cast { + struct { + unsigned int mantissa : 23; + unsigned int exponent : 8; + unsigned int sign : 1; + }; + + float f; +} __ATTRIBUTE(packed) float_cast_t; + +typedef union double_cast { + struct { + unsigned long long int mantissa : 52; + unsigned int exponent : 11; + unsigned int sign : 1; + }; + + double f; +} __ATTRIBUTE(packed) double_cast_t; + +#endif /* __NECTI_DEFINES_H__ */ diff --git a/src/LibC++/filesystem.h b/src/LibC++/filesystem.h new file mode 100644 index 0000000..807ab0d --- /dev/null +++ b/src/LibC++/filesystem.h @@ -0,0 +1,21 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifndef __NECTI_FS_H__ +#define __NECTI_FS_H__ + +#include + +/// @brief Filesystem module for LibC++ + +namespace std { +class path; +class filesystem_error; +class directory_entry; +class directory_iterator; +} // namespace std + +#endif // __NECTI_FS_H__ \ No newline at end of file diff --git a/src/LibC++/make_hdrs.sh b/src/LibC++/make_hdrs.sh new file mode 100755 index 0000000..5e8907e --- /dev/null +++ b/src/LibC++/make_hdrs.sh @@ -0,0 +1,13 @@ +#! /bin/sh + +outputDir=libCxx/ + +mkdir -p $outputDir + +for f in *.h; do + +#This line splits the file name on the delimiter "." +baseName=`echo $f | cut -d "." -f 1` +cp $f $outputDir$baseName + +done diff --git a/src/LibC++/new.cc b/src/LibC++/new.cc new file mode 100644 index 0000000..85ae82e --- /dev/null +++ b/src/LibC++/new.cc @@ -0,0 +1,7 @@ +/* ======================================== + + Copyright (C) 2025, Amlal El Mahrouss, licensed under the Apache 2.0 license. + +======================================== */ + +#include diff --git a/src/LibC++/new.h b/src/LibC++/new.h new file mode 100644 index 0000000..37e2a26 --- /dev/null +++ b/src/LibC++/new.h @@ -0,0 +1,44 @@ +/* ======================================== + + Copyright (C) 2025, Amlal El Mahrouss, licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +#include + +namespace std { +struct placement_new; + +/// ========================================================= +/// @brief Disambugate non-throwing allocation functions. +/// ========================================================= +struct nothrow_t { + explicit nothrow_t() = default; +}; + +/// ========================================================= +/// @brief Placement new metadata. +/// ========================================================= +struct placement_new final { + void* __base{}; + int32_t __align{}; + size_t __size{}; +}; + +using placement_new_t = placement_new; +} // namespace std + +void* operator new(size_t); +void* operator new[](size_t); + +void* operator new(size_t, const std::nothrow_t&) noexcept; +void* operator new(size_t, void*) noexcept; +void* operator new[](size_t, const std::nothrow_t&) noexcept; +void* operator new[](size_t, void*) noexcept; + +void operator delete(void*) noexcept; +void operator delete(void*, size_t) noexcept; + +void operator delete[](void*) noexcept; diff --git a/src/LibC++/utility.h b/src/LibC++/utility.h new file mode 100644 index 0000000..62096f5 --- /dev/null +++ b/src/LibC++/utility.h @@ -0,0 +1,30 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#ifndef LIBCXX_UTILITY_H +#define LIBCXX_UTILITY_H + +namespace std { +/// @brief Forward object. +/// @tparam Args the object type. +/// @param arg the object. +/// @return object's rvalue +template +inline auto forward(Args& arg) -> Args&& { + return static_cast(arg); +} + +/// @brief Move object. +/// @tparam Args the object type. +/// @param arg the object. +/// @return object's rvalue +template +inline auto move(Args&& arg) -> Args&& { + return static_cast(arg); +} +} // namespace std + +#endif // LIBCXX_UTILITY_H diff --git a/src/ThirdParty/Dialogs.h b/src/ThirdParty/Dialogs.h new file mode 100644 index 0000000..7264e20 --- /dev/null +++ b/src/ThirdParty/Dialogs.h @@ -0,0 +1,1739 @@ +// +// Portable File Dialogs +// +// Copyright © 2018–2022 Sam Hocevar +// +// This library is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#if _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#include +#include +#include // IFileDialog +#include +#include // GetUserProfileDirectory() +#include +#include // std::async + +#elif __EMSCRIPTEN__ +#include + +#else +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 // for popen() +#endif +#ifdef __APPLE__ +#ifndef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE +#endif +#endif +#include // fcntl() +#include // getpwnam() +#include // stat() +#include // waitpid() +#include // read(), pipe(), dup2(), getuid() +#include // ::kill, std::signal +#include // popen() +#include // std::getenv() +#endif + +#include // std::chrono +#include // std::ostream +#include // std::map +#include // std::shared_ptr +#include // std::regex +#include // std::set +#include // std::string +#include // std::mutex, std::this_thread + +// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog +#ifndef PFD_HAS_IFILEDIALOG +#define PFD_HAS_IFILEDIALOG 1 +#if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION +#if __GXX_ABI_VERSION <= 1013 +#undef PFD_HAS_IFILEDIALOG +#define PFD_HAS_IFILEDIALOG 0 +#endif +#endif +#endif + +namespace pfd { + +enum class button { + cancel = -1, + ok, + yes, + no, + abort, + retry, + ignore, +}; + +enum class choice { + ok = 0, + ok_cancel, + yes_no, + yes_no_cancel, + retry_cancel, + abort_retry_ignore, +}; + +enum class icon { + info = 0, + warning, + error, + question, +}; + +// Additional option flags for various dialog constructors +enum class opt : uint8_t { + none = 0, + // For file open, allow multiselect. + multiselect = 0x1, + // For file save, force overwrite and disable the confirmation dialog. + force_overwrite = 0x2, + // For folder select, force path to be the provided argument instead + // of the last opened directory, which is the Microsoft-recommended, + // user-friendly behaviour. + force_path = 0x4, +}; + +inline opt operator|(opt a, opt b) { + return opt(uint8_t(a) | uint8_t(b)); +} +inline bool operator&(opt a, opt b) { + return bool(uint8_t(a) & uint8_t(b)); +} + +// The settings class, only exposing to the user a way to set verbose mode +// and to force a rescan of installed desktop helpers (zenity, kdialog…). +class settings { + public: + static bool available(); + + static void verbose(bool value); + static void rescan(); + + protected: + explicit settings(bool resync = false); + + bool check_program(std::string const& program); + + inline bool is_osascript() const; + inline bool is_zenity() const; + inline bool is_kdialog() const; + + enum class flag { + is_scanned = 0, + is_verbose, + + has_zenity, + has_matedialog, + has_qarma, + has_kdialog, + is_vista, + + max_flag, + }; + + // Static array of flags for internal state + bool const& flags(flag in_flag) const; + + // Non-const getter for the static array of flags + bool& flags(flag in_flag); +}; + +// Internal classes, not to be used by client applications +namespace internal { + + // Process wait timeout, in milliseconds + static int const default_wait_timeout = 20; + + class executor { + friend class dialog; + + public: + // High level function to get the result of a command + std::string result(int* exit_code = nullptr); + + // High level function to abort + bool kill(); + +#if _WIN32 + void start_func(std::function const& fun); + static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); +#elif __EMSCRIPTEN__ + void start(int exit_code); +#else + void start_process(std::vector const& command); +#endif + + ~executor(); + + protected: + bool ready(int timeout = default_wait_timeout); + void stop(); + + private: + bool m_running = false; + std::string m_stdout; + int m_exit_code = -1; +#if _WIN32 + std::future m_future; + std::set m_windows; + std::condition_variable m_cond; + std::mutex m_mutex; + DWORD m_tid; +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something +#else + pid_t m_pid = 0; + int m_fd = -1; +#endif + }; + + class platform { + protected: +#if _WIN32 + // Helper class around LoadLibraryA() and GetProcAddress() with some safety + class dll { + public: + dll(std::string const& name); + ~dll(); + + template + class proc { + public: + proc(dll const& lib, std::string const& sym) + : m_proc(reinterpret_cast((void*) ::GetProcAddress(lib.handle, sym.c_str()))) {} + + explicit operator bool() const { return m_proc != nullptr; } + operator T*() const { return m_proc; } + + private: + T* m_proc; + }; + + private: + HMODULE handle; + }; + + // Helper class around CoInitialize() and CoUnInitialize() + class ole32_dll : public dll { + public: + ole32_dll(); + ~ole32_dll(); + bool is_initialized(); + + private: + HRESULT m_state; + }; + + // Helper class around CreateActCtx() and ActivateActCtx() + class new_style_context { + public: + new_style_context(); + ~new_style_context(); + + private: + HANDLE create(); + ULONG_PTR m_cookie = 0; + }; +#endif + }; + + class dialog : protected settings, protected platform { + public: + bool ready(int timeout = default_wait_timeout) const; + bool kill() const; + + protected: + explicit dialog(); + + std::vector desktop_helper() const; + static std::string buttons_to_name(choice _choice); + static std::string get_icon_name(icon _icon); + + std::string powershell_quote(std::string const& str) const; + std::string osascript_quote(std::string const& str) const; + std::string shell_quote(std::string const& str) const; + + // Keep handle to executing command + std::shared_ptr m_async; + }; + + class file_dialog : public dialog { + protected: + enum type { + open, + save, + folder, + }; + + file_dialog(type in_type, std::string const& title, std::string const& default_path = "", + std::vector const& filters = {}, opt options = opt::none); + + protected: + std::string string_result(); + std::vector vector_result(); + +#if _WIN32 + static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); +#if PFD_HAS_IFILEDIALOG + std::string select_folder_vista(IFileDialog* ifd, bool force_path); +#endif + + std::wstring m_wtitle; + std::wstring m_wdefault_path; + + std::vector m_vector_result; +#endif + }; + +} // namespace internal + +// +// The path class provides some platform-specific path constants +// + +class path : protected internal::platform { + public: + static std::string home(); + static std::string separator(); +}; + +// +// The notify widget +// + +class notify : public internal::dialog { + public: + notify(std::string const& title, std::string const& message, icon _icon = icon::info); +}; + +// +// The message widget +// + +class message : public internal::dialog { + public: + message(std::string const& title, std::string const& text, choice _choice = choice::ok_cancel, + icon _icon = icon::info); + + button result(); + + private: + // Some extra logic to map the exit code to button number + std::map m_mappings; +}; + +// +// The open_file, save_file, and open_folder widgets +// + +class open_file : public internal::file_dialog { + public: + open_file(std::string const& title, std::string const& default_path = "", + std::vector const& filters = {"All Files", "*"}, opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] +#endif +#endif + open_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool allow_multiselect); + + std::vector result(); +}; + +class save_file : public internal::file_dialog { + public: + save_file(std::string const& title, std::string const& default_path = "", + std::vector const& filters = {"All Files", "*"}, opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] +#endif +#endif + save_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool confirm_overwrite); + + std::string result(); +}; + +class select_folder : public internal::file_dialog { + public: + select_folder(std::string const& title, std::string const& default_path = "", + opt options = opt::none); + + std::string result(); +}; + +// +// Below this are all the method implementations. You may choose to define the +// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except +// in one place. This may reduce compilation times. +// + +#if !defined PFD_SKIP_IMPLEMENTATION + +// internal free functions implementations + +namespace internal { + +#if _WIN32 + static inline std::wstring str2wstr(std::string const& str) { + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), nullptr, 0); + std::wstring ret(len, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), (LPWSTR) ret.data(), + (int) ret.size()); + return ret; + } + + static inline std::string wstr2str(std::wstring const& str) { + int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int) str.size(), nullptr, 0, nullptr, + nullptr); + std::string ret(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int) str.size(), (LPSTR) ret.data(), + (int) ret.size(), nullptr, nullptr); + return ret; + } + + static inline bool is_vista() { + OSVERSIONINFOEXW osvi; + memset(&osvi, 0, sizeof(osvi)); + DWORDLONG const mask = VerSetConditionMask( + VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 0; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, + mask) != FALSE; + } +#endif + + // This is necessary until C++20 which will have std::string::ends_with() etc. + + static inline bool ends_with(std::string const& str, std::string const& suffix) { + return suffix.size() <= str.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + static inline bool starts_with(std::string const& str, std::string const& prefix) { + return prefix.size() <= str.size() && str.compare(0, prefix.size(), prefix) == 0; + } + + // This is necessary until C++17 which will have std::filesystem::is_directory + + static inline bool is_directory(std::string const& path) { +#if _WIN32 + auto attr = GetFileAttributesA(path.c_str()); + return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); +#elif __EMSCRIPTEN__ + // TODO + return false; +#else + struct stat s; + return stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode); +#endif + } + + // This is necessary because getenv is not thread-safe + + static inline std::string getenv(std::string const& str) { +#if _MSC_VER + char* buf = nullptr; + size_t size = 0; + if (_dupenv_s(&buf, &size, str.c_str()) == 0 && buf) { + std::string ret(buf); + free(buf); + return ret; + } + return ""; +#else + auto buf = std::getenv(str.c_str()); + return buf ? buf : ""; +#endif + } + +} // namespace internal + +// settings implementation + +inline settings::settings(bool resync) { + flags(flag::is_scanned) &= !resync; + + if (flags(flag::is_scanned)) return; + + auto pfd_verbose = internal::getenv("PFD_VERBOSE"); + auto match_no = std::regex("(|0|no|false)", std::regex_constants::icase); + if (!std::regex_match(pfd_verbose, match_no)) flags(flag::is_verbose) = true; + +#if _WIN32 + flags(flag::is_vista) = internal::is_vista(); +#elif !__APPLE__ + flags(flag::has_zenity) = check_program("zenity"); + flags(flag::has_matedialog) = check_program("matedialog"); + flags(flag::has_qarma) = check_program("qarma"); + flags(flag::has_kdialog) = check_program("kdialog"); + + // If multiple helpers are available, try to default to the best one + if (flags(flag::has_zenity) && flags(flag::has_kdialog)) { + auto desktop_name = internal::getenv("XDG_SESSION_DESKTOP"); + if (desktop_name == std::string("gnome")) + flags(flag::has_kdialog) = false; + else if (desktop_name == std::string("KDE")) + flags(flag::has_zenity) = false; + } +#endif + + flags(flag::is_scanned) = true; +} + +inline bool settings::available() { +#if _WIN32 + return true; +#elif __APPLE__ + return true; +#elif __EMSCRIPTEN__ + // FIXME: Return true after implementation is complete. + return false; +#else + settings tmp; + return tmp.flags(flag::has_zenity) || tmp.flags(flag::has_matedialog) || + tmp.flags(flag::has_qarma) || tmp.flags(flag::has_kdialog); +#endif +} + +inline void settings::verbose(bool value) { + settings().flags(flag::is_verbose) = value; +} + +inline void settings::rescan() { + settings(/* resync = */ true); +} + +// Check whether a program is present using “which”. +inline bool settings::check_program(std::string const& program) { +#if _WIN32 + (void) program; + return false; +#elif __EMSCRIPTEN__ + (void) program; + return false; +#else + int exit_code = -1; + internal::executor async; + async.start_process({"/bin/sh", "-c", "which " + program}); + async.result(&exit_code); + return exit_code == 0; +#endif +} + +inline bool settings::is_osascript() const { +#if __APPLE__ + return true; +#else + return false; +#endif +} + +inline bool settings::is_zenity() const { + return flags(flag::has_zenity) || flags(flag::has_matedialog) || flags(flag::has_qarma); +} + +inline bool settings::is_kdialog() const { + return flags(flag::has_kdialog); +} + +inline bool const& settings::flags(flag in_flag) const { + static bool flags[size_t(flag::max_flag)]; + return flags[size_t(in_flag)]; +} + +inline bool& settings::flags(flag in_flag) { + return const_cast(static_cast(this)->flags(in_flag)); +} + +// path implementation +inline std::string path::home() { +#if _WIN32 + // First try the USERPROFILE environment variable + auto user_profile = internal::getenv("USERPROFILE"); + if (user_profile.size() > 0) return user_profile; + // Otherwise, try GetUserProfileDirectory() + HANDLE token = nullptr; + DWORD len = MAX_PATH; + char buf[MAX_PATH] = {'\0'}; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + dll userenv("userenv.dll"); + dll::proc get_user_profile_directory( + userenv, "GetUserProfileDirectoryA"); + get_user_profile_directory(token, buf, &len); + CloseHandle(token); + if (*buf) return buf; + } +#elif __EMSCRIPTEN__ + return "/"; +#else + // First try the HOME environment variable + auto home = internal::getenv("HOME"); + if (home.size() > 0) return home; + // Otherwise, try getpwuid_r() + size_t len = 4096; +#if defined(_SC_GETPW_R_SIZE_MAX) + auto size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size_max != -1) len = size_t(size_max); +#endif + std::vector buf(len); + struct passwd pwd, *result; + if (getpwuid_r(getuid(), &pwd, buf.data(), buf.size(), &result) == 0) return result->pw_dir; +#endif + return "/"; +} + +inline std::string path::separator() { +#if _WIN32 + return "\\"; +#else + return "/"; +#endif +} + +// executor implementation + +inline std::string internal::executor::result(int* exit_code /* = nullptr */) { + stop(); + if (exit_code) *exit_code = m_exit_code; + return m_stdout; +} + +inline bool internal::executor::kill() { +#if _WIN32 + if (m_future.valid()) { + // Close all windows that weren’t open when we started the future + auto previous_windows = m_windows; + EnumWindows(&enum_windows_callback, (LPARAM) this); + for (auto hwnd : m_windows) + if (previous_windows.find(hwnd) == previous_windows.end()) { + SendMessage(hwnd, WM_CLOSE, 0, 0); + // Also send IDNO in case of a Yes/No or Abort/Retry/Ignore messagebox + SendMessage(hwnd, WM_COMMAND, IDNO, 0); + } + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + return false; // cannot kill +#else + ::kill(m_pid, SIGKILL); +#endif + stop(); + return true; +} + +#if _WIN32 +inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) { + auto that = (executor*) lParam; + + DWORD pid; + auto tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid == that->m_tid) that->m_windows.insert(hwnd); + return TRUE; +} +#endif + +#if _WIN32 +inline void internal::executor::start_func(std::function const& fun) { + stop(); + + auto trampoline = [fun, this]() { + // Save our thread id so that the caller can cancel us + m_tid = GetCurrentThreadId(); + EnumWindows(&enum_windows_callback, (LPARAM) this); + m_cond.notify_all(); + return fun(&m_exit_code); + }; + + std::unique_lock lock(m_mutex); + m_future = std::async(std::launch::async, trampoline); + m_cond.wait(lock); + m_running = true; +} + +#elif __EMSCRIPTEN__ +inline void internal::executor::start(int exit_code) { + m_exit_code = exit_code; +} + +#else +inline void internal::executor::start_process(std::vector const& command) { + stop(); + m_stdout.clear(); + m_exit_code = -1; + + int in[2], out[2]; + if (pipe(in) != 0 || pipe(out) != 0) return; + + m_pid = fork(); + if (m_pid < 0) return; + + close(in[m_pid ? 0 : 1]); + close(out[m_pid ? 1 : 0]); + + if (m_pid == 0) { + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + + // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) + int fd = open("/src/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + close(fd); + + std::vector args; + std::transform(command.cbegin(), command.cend(), std::back_inserter(args), + [](std::string const& s) { return const_cast(s.c_str()); }); + args.push_back(nullptr); // null-terminate argv[] + + execvp(args[0], args.data()); + exit(1); + } + + close(in[1]); + m_fd = out[0]; + auto flags = fcntl(m_fd, F_GETFL); + fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); + + m_running = true; +} +#endif + +inline internal::executor::~executor() { + stop(); +} + +inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) { + if (!m_running) return true; + +#if _WIN32 + if (m_future.valid()) { + auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); + if (status != std::future_status::ready) { + // On Windows, we need to run the message pump. If the async + // thread uses a Windows API dialog, it may be attached to the + // main thread and waiting for messages that only we can dispatch. + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return false; + } + + m_stdout = m_future.get(); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void) timeout; +#else + char buf[BUFSIZ]; + ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore + if (received > 0) { + m_stdout += std::string(buf, received); + return false; + } + + // Reap child process if it is dead. It is possible that the system has already reaped it + // (this happens when the calling application handles or ignores SIG_CHLD) and results in + // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for + // a little while. + int status; + pid_t child = waitpid(m_pid, &status, WNOHANG); + if (child != m_pid && (child >= 0 || errno != ECHILD)) { + // FIXME: this happens almost always at first iteration + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + return false; + } + + close(m_fd); + m_exit_code = WEXITSTATUS(status); +#endif + + m_running = false; + return true; +} + +inline void internal::executor::stop() { + // Loop until the user closes the dialog + while (!ready()) + ; +} + +// dll implementation + +#if _WIN32 +inline internal::platform::dll::dll(std::string const& name) + : handle(::LoadLibraryA(name.c_str())) {} + +inline internal::platform::dll::~dll() { + if (handle) ::FreeLibrary(handle); +} +#endif // _WIN32 + +// ole32_dll implementation + +#if _WIN32 +inline internal::platform::ole32_dll::ole32_dll() : dll("ole32.dll") { + // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. + // See https://github.com/samhocevar/portable-file-dialogs/issues/51 + auto coinit = proc(*this, "CoInitializeEx"); + m_state = coinit(nullptr, COINIT_MULTITHREADED); +} + +inline internal::platform::ole32_dll::~ole32_dll() { + if (is_initialized()) proc(*this, "CoUninitialize")(); +} + +inline bool internal::platform::ole32_dll::is_initialized() { + return m_state == S_OK || m_state == S_FALSE; +} +#endif + +// new_style_context implementation + +#if _WIN32 +inline internal::platform::new_style_context::new_style_context() { + // Only create one activation context for the whole app lifetime. + static HANDLE hctx = create(); + + if (hctx != INVALID_HANDLE_VALUE) ActivateActCtx(hctx, &m_cookie); +} + +inline internal::platform::new_style_context::~new_style_context() { + DeactivateActCtx(0, m_cookie); +} + +inline HANDLE internal::platform::new_style_context::create() { + // This “hack” seems to be necessary for this code to work on windows XP. + // Without it, dialogs do not show and close immediately. GetError() + // returns 0 so I don’t know what causes this. I was not able to reproduce + // this behavior on Windows 7 and 10 but just in case, let it be here for + // those versions too. + // This hack is not required if other dialogs are used (they load comdlg32 + // automatically), only if message boxes are used. + dll comdlg32("comdlg32.dll"); + + // Using approach as shown here: https://stackoverflow.com/a/10444161 + UINT len = ::GetSystemDirectoryA(nullptr, 0); + std::string sys_dir(len, '\0'); + ::GetSystemDirectoryA(&sys_dir[0], len); + + ACTCTXA act_ctx = { + // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a + // crash with error “default context is already set”. + sizeof(act_ctx), + ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, + "shell32.dll", + 0, + 0, + sys_dir.c_str(), + (LPCSTR) 124, + nullptr, + 0, + }; + + return ::CreateActCtxA(&act_ctx); +} +#endif // _WIN32 + +// dialog implementation + +inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const { + return m_async->ready(timeout); +} + +inline bool internal::dialog::kill() const { + return m_async->kill(); +} + +inline internal::dialog::dialog() : m_async(std::make_shared()) {} + +inline std::vector internal::dialog::desktop_helper() const { +#if __APPLE__ + return {"osascript"}; +#else + return {flags(flag::has_zenity) ? "zenity" + : flags(flag::has_matedialog) ? "matedialog" + : flags(flag::has_qarma) ? "qarma" + : flags(flag::has_kdialog) ? "kdialog" + : "echo"}; +#endif +} + +inline std::string internal::dialog::buttons_to_name(choice _choice) { + switch (_choice) { + case choice::ok_cancel: + return "okcancel"; + case choice::yes_no: + return "yesno"; + case choice::yes_no_cancel: + return "yesnocancel"; + case choice::retry_cancel: + return "retrycancel"; + case choice::abort_retry_ignore: + return "abortretryignore"; + /* case choice::ok: */ default: + return "ok"; + } +} + +inline std::string internal::dialog::get_icon_name(icon _icon) { + switch (_icon) { + case icon::warning: + return "warning"; + case icon::error: + return "error"; + case icon::question: + return "question"; + // Zenity wants "information" but WinForms wants "info" + /* case icon::info: */ default: +#if _WIN32 + return "info"; +#else + return "information"; +#endif + } +} + +// This is only used for debugging purposes +inline std::ostream& operator<<(std::ostream& s, std::vector const& v) { + int not_first = 0; + for (auto& e : v) s << (not_first++ ? " " : "") << e; + return s; +} + +// Properly quote a string for Powershell: replace ' or " with '' or "" +// FIXME: we should probably get rid of newlines! +// FIXME: the \" sequence seems unsafe, too! +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::powershell_quote(std::string const& str) const { + return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; +} + +// Properly quote a string for osascript: replace \ or " with \\ or \" +// XXX: this also used to replace ' with \' when popen was used, but it would be +// smarter to do shell_quote(osascript_quote(...)) if this is needed again. +inline std::string internal::dialog::osascript_quote(std::string const& str) const { + return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; +} + +// Properly quote a string for the shell: just replace ' with '\'' +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::shell_quote(std::string const& str) const { + return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; +} + +// file_dialog implementation + +inline internal::file_dialog::file_dialog(type in_type, std::string const& title, + std::string const& default_path /* = "" */, + std::vector const& filters /* = {} */, + opt options /* = opt::none */) { +#if _WIN32 + std::string filter_list; + std::regex whitespace(" *"); + for (size_t i = 0; i + 1 < filters.size(); i += 2) { + filter_list += filters[i] + '\0'; + filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; + } + filter_list += '\0'; + + m_async->start_func( + [this, in_type, title, default_path, filter_list, options](int* exit_code) -> std::string { + (void) exit_code; + m_wtitle = internal::str2wstr(title); + m_wdefault_path = internal::str2wstr(default_path); + auto wfilter_list = internal::str2wstr(filter_list); + + // Initialise COM. This is required for the new folder selection window, + // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) + // and to avoid random crashes with GetOpenFileNameW() (see + // https://github.com/samhocevar/portable-file-dialogs/issues/51) + ole32_dll ole32; + + // Folder selection uses a different method + if (in_type == type::folder) { +#if PFD_HAS_IFILEDIALOG + if (flags(flag::is_vista)) { + // On Vista and higher we should be able to use IFileDialog for folder selection + IFileDialog* ifd; + HRESULT hr = dll::proc( + ole32, "CoCreateInstance")(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&ifd)); + + // In case CoCreateInstance fails (which it should not), try legacy approach + if (SUCCEEDED(hr)) return select_folder_vista(ifd, options & opt::force_path); + } +#endif + + BROWSEINFOW bi; + memset(&bi, 0, sizeof(bi)); + + bi.lpfn = &bffcallback; + bi.lParam = (LPARAM) this; + + if (flags(flag::is_vista)) { + if (ole32.is_initialized()) bi.ulFlags |= BIF_NEWDIALOGSTYLE; + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_STATUSTEXT; + } + + auto* list = SHBrowseForFolderW(&bi); + std::string ret; + if (list) { + auto buffer = new wchar_t[MAX_PATH]; + SHGetPathFromIDListW(list, buffer); + dll::proc(ole32, "CoTaskMemFree")(list); + ret = internal::wstr2str(buffer); + delete[] buffer; + } + return ret; + } + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetActiveWindow(); + + ofn.lpstrFilter = wfilter_list.c_str(); + + auto woutput = std::wstring(MAX_PATH * 256, L'\0'); + ofn.lpstrFile = (LPWSTR) woutput.data(); + ofn.nMaxFile = (DWORD) woutput.size(); + if (!m_wdefault_path.empty()) { + // If a directory was provided, use it as the initial directory. If + // a valid path was provided, use it as the initial file. Otherwise, + // let the Windows API decide. + auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); + if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) + ofn.lpstrInitialDir = m_wdefault_path.c_str(); + else if (m_wdefault_path.size() <= woutput.size()) + // second argument is size of buffer, not length of string + StringCchCopyW(ofn.lpstrFile, MAX_PATH * 256 + 1, m_wdefault_path.c_str()); + else { + ofn.lpstrFileTitle = (LPWSTR) m_wdefault_path.data(); + ofn.nMaxFileTitle = (DWORD) m_wdefault_path.size(); + } + } + ofn.lpstrTitle = m_wtitle.c_str(); + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + + dll comdlg32("comdlg32.dll"); + + // Apply new visual style (required for windows XP) + new_style_context ctx; + + if (in_type == type::save) { + if (!(options & opt::force_overwrite)) ofn.Flags |= OFN_OVERWRITEPROMPT; + + dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); + if (get_save_file_name(&ofn) == 0) return ""; + return internal::wstr2str(woutput.c_str()); + } else { + if (options & opt::multiselect) ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_PATHMUSTEXIST; + + dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); + if (get_open_file_name(&ofn) == 0) return ""; + } + + std::string prefix; + for (wchar_t const* p = woutput.c_str(); *p;) { + auto filename = internal::wstr2str(p); + p += wcslen(p); + // In multiselect mode, we advance p one wchar further and + // check for another filename. If there is one and the + // prefix is empty, it means we just read the prefix. + if ((options & opt::multiselect) && *++p && prefix.empty()) { + prefix = filename + "/"; + continue; + } + + m_vector_result.push_back(prefix + filename); + } + + return ""; + }); +#elif __EMSCRIPTEN__ + // FIXME: do something + (void) in_type; + (void) title; + (void) default_path; + (void) filters; + (void) options; +#else + auto command = desktop_helper(); + + if (is_osascript()) { + std::string script = "set ret to choose"; + switch (in_type) { + case type::save: + script += " file name"; + break; + case type::open: + default: + script += " file"; + if (options & opt::multiselect) script += " with multiple selections allowed"; + break; + case type::folder: + script += " folder"; + break; + } + + if (default_path.size()) { + if (in_type == type::folder || is_directory(default_path)) + script += " default location "; + else + script += " default name "; + script += osascript_quote(default_path); + } + + script += " with prompt " + osascript_quote(title); + + if (in_type == type::open) { + // Concatenate all user-provided filter patterns + std::string patterns; + for (size_t i = 0; i < filters.size() / 2; ++i) patterns += " " + filters[2 * i + 1]; + + // Split the pattern list to check whether "*" is in there; if it + // is, we have to disable filters because there is no mechanism in + // OS X for the user to override the filter. + std::regex sep("\\s+"); + std::string filter_list; + bool has_filter = true; + std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); + std::sregex_token_iterator end; + for (; iter != end; ++iter) { + auto pat = iter->str(); + if (pat == "*" || pat == "*.*") + has_filter = false; + else if (internal::starts_with(pat, "*.")) + filter_list += "," + osascript_quote(pat.substr(2, pat.size() - 2)); + } + + if (has_filter && filter_list.size() > 0) { + // There is a weird AppleScript bug where file extensions of length != 3 are + // ignored, e.g. type{"txt"} works, but type{"json"} does not. Fortunately if + // the whole list starts with a 3-character extension, everything works again. + // We use "///" for such an extension because we are sure it cannot appear in + // an actual filename. + script += " of type {\"///\"" + filter_list + "}"; + } + } + + if (in_type == type::open && (options & opt::multiselect)) { + script += "\nset s to \"\""; + script += "\nrepeat with i in ret"; + script += "\n set s to s & (POSIX path of i) & \"\\n\""; + script += "\nend repeat"; + script += "\ncopy s to stdout"; + } else { + script += "\nPOSIX path of ret"; + } + + command.push_back("-e"); + command.push_back(script); + } else if (is_zenity()) { + command.push_back("--file-selection"); + + // If the default path is a directory, make sure it ends with "/" otherwise zenity will + // open the file dialog in the parent directory. + auto filename_arg = "--filename=" + default_path; + if (in_type != type::folder && !ends_with(default_path, "/") && + internal::is_directory(default_path)) + filename_arg += "/"; + command.push_back(filename_arg); + + command.push_back("--title"); + command.push_back(title); + command.push_back("--separator=\n"); + + for (size_t i = 0; i < filters.size() / 2; ++i) { + command.push_back("--file-filter"); + command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); + } + + if (in_type == type::save) command.push_back("--save"); + if (in_type == type::folder) command.push_back("--directory"); + if (!(options & opt::force_overwrite)) command.push_back("--confirm-overwrite"); + if (options & opt::multiselect) command.push_back("--multiple"); + } else if (is_kdialog()) { + switch (in_type) { + case type::save: + command.push_back("--getsavefilename"); + break; + case type::open: + command.push_back("--getopenfilename"); + break; + case type::folder: + command.push_back("--getexistingdirectory"); + break; + } + if (options & opt::multiselect) { + command.push_back("--multiple"); + command.push_back("--separate-output"); + } + + command.push_back(default_path); + + std::string filter; + for (size_t i = 0; i < filters.size() / 2; ++i) + filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; + command.push_back(filter); + + command.push_back("--title"); + command.push_back(title); + } + + if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline std::string internal::file_dialog::string_result() { +#if _WIN32 + return m_async->result(); +#else + auto ret = m_async->result(); + // Strip potential trailing newline (zenity). Also strip trailing slash + // added by osascript for consistency with other backends. + while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) ret.pop_back(); + return ret; +#endif +} + +inline std::vector internal::file_dialog::vector_result() { +#if _WIN32 + m_async->result(); + return m_vector_result; +#else + std::vector ret; + auto result = m_async->result(); + for (;;) { + // Split result along newline characters + auto i = result.find('\n'); + if (i == 0 || i == std::string::npos) break; + ret.push_back(result.substr(0, i)); + result = result.substr(i + 1, result.size()); + } + return ret; +#endif +} + +#if _WIN32 +// Use a static function to pass as BFFCALLBACK for legacy folder select +inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData) { + auto inst = (file_dialog*) pData; + switch (uMsg) { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) inst->m_wdefault_path.c_str()); + break; + } + return 0; +} + +#if PFD_HAS_IFILEDIALOG +inline std::string internal::file_dialog::select_folder_vista(IFileDialog* ifd, bool force_path) { + std::string result; + + IShellItem* folder; + + // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) + dll shell32("shell32.dll"); + dll::proc create_item( + shell32, "SHCreateItemFromParsingName"); + + if (!create_item) return ""; + + auto hr = create_item(m_wdefault_path.c_str(), nullptr, IID_PPV_ARGS(&folder)); + + // Set default folder if found. This only sets the default folder. If + // Windows has any info about the most recently selected folder, it + // will display it instead. Generally, calling SetFolder() to set the + // current directory “is not a good or expected user experience and + // should therefore be avoided”: + // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder + if (SUCCEEDED(hr)) { + if (force_path) + ifd->SetFolder(folder); + else + ifd->SetDefaultFolder(folder); + folder->Release(); + } + + // Set the dialog title and option to select folders + ifd->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); + ifd->SetTitle(m_wtitle.c_str()); + + hr = ifd->Show(GetActiveWindow()); + if (SUCCEEDED(hr)) { + IShellItem* item; + hr = ifd->GetResult(&item); + if (SUCCEEDED(hr)) { + wchar_t* wname = nullptr; + // This is unlikely to fail because we use FOS_FORCEFILESYSTEM, but try + // to output a debug message just in case. + if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &wname))) { + result = internal::wstr2str(std::wstring(wname)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wname); + } else { + if (SUCCEEDED(item->GetDisplayName(SIGDN_NORMALDISPLAY, &wname))) { + auto name = internal::wstr2str(std::wstring(wname)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wname); + std::cerr << "pfd: failed to get path for " << name << std::endl; + } else + std::cerr << "pfd: item of unknown type selected" << std::endl; + } + + item->Release(); + } + } + + ifd->Release(); + + return result; +} +#endif +#endif + +// notify implementation + +inline notify::notify(std::string const& title, std::string const& message, + icon _icon /* = icon::info */) { + if (_icon == icon::question) // Not supported by notifications + _icon = icon::info; + +#if _WIN32 + // Use a static shared pointer for notify_icon so that we can delete + // it whenever we need to display a new one, and we can also wait + // until the program has finished running. + struct notify_icon_data : public NOTIFYICONDATAW { + ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } + }; + + static std::shared_ptr nid; + + // Release the previous notification icon, if any, and allocate a new + // one. Note that std::make_shared() does value initialization, so there + // is no need to memset the structure. + nid = nullptr; + nid = std::make_shared(); + + // For XP support + nid->cbSize = NOTIFYICONDATAW_V2_SIZE; + nid->hWnd = nullptr; + nid->uID = 0; + + // Flag Description: + // - NIF_ICON The hIcon member is valid. + // - NIF_MESSAGE The uCallbackMessage member is valid. + // - NIF_TIP The szTip member is valid. + // - NIF_STATE The dwState and dwStateMask members are valid. + // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, + // szInfoTitle, and dwInfoFlags members are valid. + // - NIF_GUID Reserved. + nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; + + // Flag Description + // - NIIF_ERROR An error icon. + // - NIIF_INFO An information icon. + // - NIIF_NONE No icon. + // - NIIF_WARNING A warning icon. + // - NIIF_ICON_MASK Version 6.0. Reserved. + // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon + // ToolTips + switch (_icon) { + case icon::warning: + nid->dwInfoFlags = NIIF_WARNING; + break; + case icon::error: + nid->dwInfoFlags = NIIF_ERROR; + break; + /* case icon::info: */ default: + nid->dwInfoFlags = NIIF_INFO; + break; + } + + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, + LONG_PTR lParam) -> BOOL { + ((NOTIFYICONDATAW*) lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; + }; + + nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR) nid.get()); + + nid->uTimeout = 5000; + + StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); + StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); + + // Display the new icon + Shell_NotifyIconW(NIM_ADD, nid.get()); +#elif __EMSCRIPTEN__ + // FIXME: do something + (void) title; + (void) message; +#else + auto command = desktop_helper(); + + if (is_osascript()) { + command.push_back("-e"); + command.push_back("display notification " + osascript_quote(message) + " with title " + + osascript_quote(title)); + } else if (is_zenity()) { + command.push_back("--notification"); + command.push_back("--window-icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--text"); + command.push_back(title + "\n" + message); + } else if (is_kdialog()) { + command.push_back("--icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--title"); + command.push_back(title); + command.push_back("--passivepopup"); + command.push_back(message); + command.push_back("5"); + } + + if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +// message implementation + +inline message::message(std::string const& title, std::string const& text, + choice _choice /* = choice::ok_cancel */, icon _icon /* = icon::info */) { +#if _WIN32 + // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought + // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 + UINT style = MB_SYSTEMMODAL; + switch (_icon) { + case icon::warning: + style |= MB_ICONWARNING; + break; + case icon::error: + style |= MB_ICONERROR; + break; + case icon::question: + style |= MB_ICONQUESTION; + break; + /* case icon::info: */ default: + style |= MB_ICONINFORMATION; + break; + } + + switch (_choice) { + case choice::ok_cancel: + style |= MB_OKCANCEL; + break; + case choice::yes_no: + style |= MB_YESNO; + break; + case choice::yes_no_cancel: + style |= MB_YESNOCANCEL; + break; + case choice::retry_cancel: + style |= MB_RETRYCANCEL; + break; + case choice::abort_retry_ignore: + style |= MB_ABORTRETRYIGNORE; + break; + /* case choice::ok: */ default: + style |= MB_OK; + break; + } + + m_mappings[IDCANCEL] = button::cancel; + m_mappings[IDOK] = button::ok; + m_mappings[IDYES] = button::yes; + m_mappings[IDNO] = button::no; + m_mappings[IDABORT] = button::abort; + m_mappings[IDRETRY] = button::retry; + m_mappings[IDIGNORE] = button::ignore; + + m_async->start_func([text, title, style](int* exit_code) -> std::string { + auto wtext = internal::str2wstr(text); + auto wtitle = internal::str2wstr(title); + // Apply new visual style (required for all Windows versions) + new_style_context ctx; + *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); + return ""; + }); + +#elif __EMSCRIPTEN__ + std::string full_message; + switch (_icon) { + case icon::warning: + full_message = "⚠️"; + break; + case icon::error: + full_message = "⛔"; + break; + case icon::question: + full_message = "❓"; + break; + /* case icon::info: */ default: + full_message = "ℹ"; + break; + } + + full_message += ' ' + title + "\n\n" + text; + + // This does not really start an async task; it just passes the + // EM_ASM_INT return value to a fake start() function. + m_async->start(EM_ASM_INT( + { + if ($1) return window.confirm(UTF8ToString($0)) ? 0 : -1; + alert(UTF8ToString($0)); + return 0; + }, + full_message.c_str(), _choice == choice::ok_cancel)); +#else + auto command = desktop_helper(); + + if (is_osascript()) { + std::string script = + "display dialog " + osascript_quote(text) + " with title " + osascript_quote(title); + auto if_cancel = button::cancel; + switch (_choice) { + case choice::ok_cancel: + script += + "buttons {\"OK\", \"Cancel\"}" + " default button \"OK\"" + " cancel button \"Cancel\""; + break; + case choice::yes_no: + script += + "buttons {\"Yes\", \"No\"}" + " default button \"Yes\"" + " cancel button \"No\""; + if_cancel = button::no; + break; + case choice::yes_no_cancel: + script += + "buttons {\"Yes\", \"No\", \"Cancel\"}" + " default button \"Yes\"" + " cancel button \"Cancel\""; + break; + case choice::retry_cancel: + script += + "buttons {\"Retry\", \"Cancel\"}" + " default button \"Retry\"" + " cancel button \"Cancel\""; + break; + case choice::abort_retry_ignore: + script += + "buttons {\"Abort\", \"Retry\", \"Ignore\"}" + " default button \"Abort\"" + " cancel button \"Retry\""; + if_cancel = button::retry; + break; + case choice::ok: + default: + script += + "buttons {\"OK\"}" + " default button \"OK\"" + " cancel button \"OK\""; + if_cancel = button::ok; + break; + } + m_mappings[1] = if_cancel; + m_mappings[256] = if_cancel; // XXX: I think this was never correct + script += " with icon "; + switch (_icon) { +#define PFD_OSX_ICON(n) \ + "alias ((path to library folder from system domain) as text " \ + "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" + case icon::info: + default: + script += PFD_OSX_ICON("ToolBarInfo"); + break; + case icon::warning: + script += "caution"; + break; + case icon::error: + script += "stop"; + break; + case icon::question: + script += PFD_OSX_ICON("GenericQuestionMarkIcon"); + break; +#undef PFD_OSX_ICON + } + + command.push_back("-e"); + command.push_back(script); + } else if (is_zenity()) { + switch (_choice) { + case choice::ok_cancel: + command.insert(command.end(), {"--question", "--cancel-label=Cancel", "--ok-label=OK"}); + break; + case choice::yes_no: + // Do not use standard --question because it causes “No” to return -1, + // which is inconsistent with the “Yes/No/Cancel” mode below. + command.insert(command.end(), + {"--question", "--switch", "--extra-button=No", "--extra-button=Yes"}); + break; + case choice::yes_no_cancel: + command.insert(command.end(), {"--question", "--switch", "--extra-button=Cancel", + "--extra-button=No", "--extra-button=Yes"}); + break; + case choice::retry_cancel: + command.insert(command.end(), + {"--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry"}); + break; + case choice::abort_retry_ignore: + command.insert(command.end(), {"--question", "--switch", "--extra-button=Ignore", + "--extra-button=Abort", "--extra-button=Retry"}); + break; + case choice::ok: + default: + switch (_icon) { + case icon::error: + command.push_back("--error"); + break; + case icon::warning: + command.push_back("--warning"); + break; + default: + command.push_back("--info"); + break; + } + } + + command.insert(command.end(), + {"--title", title, "--width=300", "--height=0", // sensible defaults + "--no-markup", // do not interpret text as Pango markup + "--text", text, "--icon-name=dialog-" + get_icon_name(_icon)}); + } else if (is_kdialog()) { + if (_choice == choice::ok) { + switch (_icon) { + case icon::error: + command.push_back("--error"); + break; + case icon::warning: + command.push_back("--sorry"); + break; + default: + command.push_back("--msgbox"); + break; + } + } else { + std::string flag = "--"; + if (_icon == icon::warning || _icon == icon::error) flag += "warning"; + flag += "yesno"; + if (_choice == choice::yes_no_cancel) flag += "cancel"; + command.push_back(flag); + if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) { + m_mappings[0] = button::yes; + m_mappings[256] = button::no; + } + } + + command.push_back(text); + command.push_back("--title"); + command.push_back(title); + + // Must be after the above part + if (_choice == choice::ok_cancel) + command.insert(command.end(), {"--yes-label", "OK", "--no-label", "Cancel"}); + } + + if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline button message::result() { + int exit_code; + auto ret = m_async->result(&exit_code); + // osascript will say "button returned:Cancel\n" + // and others will just say "Cancel\n" + if (internal::ends_with(ret, "Cancel\n")) return button::cancel; + if (internal::ends_with(ret, "OK\n")) return button::ok; + if (internal::ends_with(ret, "Yes\n")) return button::yes; + if (internal::ends_with(ret, "No\n")) return button::no; + if (internal::ends_with(ret, "Abort\n")) return button::abort; + if (internal::ends_with(ret, "Retry\n")) return button::retry; + if (internal::ends_with(ret, "Ignore\n")) return button::ignore; + if (m_mappings.count(exit_code) != 0) return m_mappings[exit_code]; + return exit_code == 0 ? button::ok : button::cancel; +} + +// open_file implementation + +inline open_file::open_file(std::string const& title, std::string const& default_path /* = "" */, + std::vector const& filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::open, title, default_path, filters, options) {} + +inline open_file::open_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool allow_multiselect) + : open_file(title, default_path, filters, (allow_multiselect ? opt::multiselect : opt::none)) {} + +inline std::vector open_file::result() { + return vector_result(); +} + +// save_file implementation + +inline save_file::save_file(std::string const& title, std::string const& default_path /* = "" */, + std::vector const& filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::save, title, default_path, filters, options) {} + +inline save_file::save_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool confirm_overwrite) + : save_file(title, default_path, filters, + (confirm_overwrite ? opt::none : opt::force_overwrite)) {} + +inline std::string save_file::result() { + return string_result(); +} + +// select_folder implementation + +inline select_folder::select_folder(std::string const& title, + std::string const& default_path /* = "" */, + opt options /* = opt::none */) + : file_dialog(type::folder, title, default_path, {}, options) {} + +inline std::string select_folder::result() { + return string_result(); +} + +#endif // PFD_SKIP_IMPLEMENTATION + +} // namespace pfd -- cgit v1.2.3 From eb7f40b000b0b1dc4605113f3f2d7e87003d6d43 Mon Sep 17 00:00:00 2001 From: Amlal El Mahrouss Date: Wed, 26 Nov 2025 19:46:58 +0100 Subject: chore: fix compile_flags.txt and codebase choring. Signed-off-by: Amlal El Mahrouss --- compile_flags.txt | 8 ++++---- docs/md/SPECIFICATION_CC.md | 23 ----------------------- docs/md/SPECIFICATION_COMPILERKIT.md | 23 +++++++++++++++++++++++ src/CompilerKit/src/Frontend/CCompiler64x0.cc | 2 +- src/CompilerKit/src/Frontend/CCompilerARM64.cc | 2 +- src/CompilerKit/src/Linker/DynamicLinker64PEF.cc | 4 ++-- tools/cppdrv.cc | 2 +- tools/dbg.cc | 2 +- tools/pef-amd64-cxxdrv.cc | 2 +- tools/pef-arm64-cdrv.cc | 2 +- 10 files changed, 35 insertions(+), 35 deletions(-) delete mode 100644 docs/md/SPECIFICATION_CC.md create mode 100644 docs/md/SPECIFICATION_COMPILERKIT.md (limited to 'src') diff --git a/compile_flags.txt b/compile_flags.txt index 2583106..b40bbf0 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,8 +1,8 @@ -std=c++20 --Idev/ --Idev/src/ --Idev/CompilerKit/Detail --Isdk/ +-Isrc/ +-Isrc/src/ +-Isrc/CompilerKit/Detail +-Ivendor/ -I./ -DCK_USE_STRUCTS -xc++ diff --git a/docs/md/SPECIFICATION_CC.md b/docs/md/SPECIFICATION_CC.md deleted file mode 100644 index 681cfde..0000000 --- a/docs/md/SPECIFICATION_CC.md +++ /dev/null @@ -1,23 +0,0 @@ -# Specification of CompilerKit - -=================================== - -# 0: General Information - -=================================== -- Output format: PEF, XCOFF -- Object format: AE, COFF -- SDK Language: C++ - -=================================== - -# 1: CompilerKit - -=================================== - -- Shall support compilation, assembling and linking. -- Shall be written in C++ -- Shall support the assembler too. -- Shall provide primitives for compilers, assemblers and linkers. -- Shall support multiple architectures. -- Shall have a debugger. \ No newline at end of file diff --git a/docs/md/SPECIFICATION_COMPILERKIT.md b/docs/md/SPECIFICATION_COMPILERKIT.md new file mode 100644 index 0000000..681cfde --- /dev/null +++ b/docs/md/SPECIFICATION_COMPILERKIT.md @@ -0,0 +1,23 @@ +# Specification of CompilerKit + +=================================== + +# 0: General Information + +=================================== +- Output format: PEF, XCOFF +- Object format: AE, COFF +- SDK Language: C++ + +=================================== + +# 1: CompilerKit + +=================================== + +- Shall support compilation, assembling and linking. +- Shall be written in C++ +- Shall support the assembler too. +- Shall provide primitives for compilers, assemblers and linkers. +- Shall support multiple architectures. +- Shall have a debugger. \ No newline at end of file diff --git a/src/CompilerKit/src/Frontend/CCompiler64x0.cc b/src/CompilerKit/src/Frontend/CCompiler64x0.cc index f07fcb0..bed4ddd 100644 --- a/src/CompilerKit/src/Frontend/CCompiler64x0.cc +++ b/src/CompilerKit/src/Frontend/CCompiler64x0.cc @@ -1188,7 +1188,7 @@ class AssemblyCCInterface final CK_ASSEMBLY_INTERFACE { #include #define kPrintF printf -#define kSplashCxx() kPrintF(kWhite "NE C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) +#define kSplashCxx() kPrintF(kWhite "NeCTI C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) static void cc_print_help() { kSplashCxx(); diff --git a/src/CompilerKit/src/Frontend/CCompilerARM64.cc b/src/CompilerKit/src/Frontend/CCompilerARM64.cc index c5b1fb8..ea39a31 100644 --- a/src/CompilerKit/src/Frontend/CCompilerARM64.cc +++ b/src/CompilerKit/src/Frontend/CCompilerARM64.cc @@ -1187,7 +1187,7 @@ class AssemblyCCInterface final CK_ASSEMBLY_INTERFACE { #include #define kPrintF printf -#define kSplashCxx() kPrintF(kWhite "NE C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) +#define kSplashCxx() kPrintF(kWhite "NeCTI C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) static void cc_print_help() { kSplashCxx(); diff --git a/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc b/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc index 8a503ed..176a608 100644 --- a/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc +++ b/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc @@ -67,8 +67,8 @@ static CompilerKit::STLString kLinkerStart = kPefStart; static std::vector kObjectList; static std::vector kObjectBytes; -/// @brief NE 64-bit Linker. -/// @note This linker is made for PEF executable, thus NE based OSes. +/// @brief NeCTI 64-bit Linker. +/// @note This linker is made for PEF executable, thus NeCTI based OSes. NECTI_MODULE(DynamicLinker64PEF) { bool is_executable = true; diff --git a/tools/cppdrv.cc b/tools/cppdrv.cc index 4d6f7cf..2cccf2b 100644 --- a/tools/cppdrv.cc +++ b/tools/cppdrv.cc @@ -5,7 +5,7 @@ ======================================== */ /// @file cxxdrv.cc -/// @brief NE frontend preprocessor. +/// @brief NeCTI frontend preprocessor. #include #include diff --git a/tools/dbg.cc b/tools/dbg.cc index 854351e..a54c59e 100644 --- a/tools/dbg.cc +++ b/tools/dbg.cc @@ -7,7 +7,7 @@ #include /// @file dbg.cc -/// @brief NE debugger. +/// @brief NeCTI debugger. CK_IMPORT_C Int32 DebuggerMachPOSIX(Int32 argc, Char const* argv[]); diff --git a/tools/pef-amd64-cxxdrv.cc b/tools/pef-amd64-cxxdrv.cc index e1187e8..3fc41fe 100644 --- a/tools/pef-amd64-cxxdrv.cc +++ b/tools/pef-amd64-cxxdrv.cc @@ -5,7 +5,7 @@ ======================================== */ /// @file cxxdrv.cc -/// @brief NE C++ frontend compiler. +/// @brief NeCTI C++ frontend compiler. #include #include diff --git a/tools/pef-arm64-cdrv.cc b/tools/pef-arm64-cdrv.cc index b7d8ed1..0a4a9af 100644 --- a/tools/pef-arm64-cdrv.cc +++ b/tools/pef-arm64-cdrv.cc @@ -5,7 +5,7 @@ ======================================== */ /// @file cxxdrv.cc -/// @brief NE C++ frontend compiler. +/// @brief NeCTI C++ frontend compiler. #include #include -- cgit v1.2.3