Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9cb8cea
FEAT: Add xUnit for C# unit testing
rory-cd Jul 29, 2026
d5b26da
NEW: Add C# unit test for Color
rory-cd Jul 29, 2026
70ceff0
Add parser to analyse the unit tests syntax
rory-cd Aug 2, 2026
16056d8
Add AST data structure and JSON serialisation
rory-cd Aug 5, 2026
ff3b34a
Add Catch2 macros to AST
rory-cd Aug 5, 2026
e47218f
Add macro detection and statement data structures
rory-cd Aug 8, 2026
e879634
Add more expression and assertion support
rory-cd Aug 11, 2026
ea0b304
Split source code across multiple files
rory-cd Aug 11, 2026
0e095bf
Update CmakeLists for new source files
rory-cd Aug 11, 2026
86f7e0c
Minor clean ups
rory-cd Aug 11, 2026
bbbcdd9
Add translator module
rory-cd Aug 12, 2026
5211eac
Switch from JSON to in-memory AST
rory-cd Aug 12, 2026
5488917
Adjust AST structure with fewer pointers
rory-cd Aug 13, 2026
6c1e112
Improve translation - basic tests translate correctly
rory-cd Aug 13, 2026
9be2b92
Move files to tools directory
rory-cd Aug 14, 2026
f6a977e
Improve syntax coverage and fix macro sharing across files
rory-cd Aug 18, 2026
bf4ebe9
Fix handling overloaded operators
rory-cd Aug 19, 2026
5e73026
Flatten nested sections in AST
rory-cd Aug 19, 2026
b2c8202
Add support for enums and multiple VarDecls per line
rory-cd Aug 20, 2026
115e94b
Add support for translating ctor and init list
rory-cd Sep 1, 2026
bd808cf
Add support for nested types and reference parameters
rory-cd Sep 10, 2026
b7e45bb
Add additional syntax support, language swap option
rory-cd Sep 20, 2026
4f984af
Add syntax support for more tests
rory-cd Sep 20, 2026
dc85d53
Remove included nlohmann header file
rory-cd Sep 20, 2026
65faf21
Add xUnit using statement back in
rory-cd Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tools/transpiler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
generated/
build/
.vscode
.cache
63 changes: 63 additions & 0 deletions tools/transpiler/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# CMake file for building the transpiler
#

cmake_minimum_required(VERSION 3.10)

project(SplashKitTranspiler) # Define project name

set(CMAKE_CXX_STANDARD 17) # Set C++ required version

# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) # Put the executable in the main directory
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Add source files to executable
add_executable(Transpiler
src/main.cpp
src/parser.cpp
src/builder.cpp
src/translator.cpp
)

# Define CMake variables
set(SPLASHKIT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..")
set(SPLASHKIT_SRC "${SPLASHKIT_ROOT}/coresdk/src")
set(SPLASHKIT_BACKEND "${SPLASHKIT_SRC}/backend")
set(SPLASHKIT_CORESDK "${SPLASHKIT_SRC}/coresdk")
set(SPLASHKIT_CATCH "${SPLASHKIT_ROOT}/coresdk/external/catch")
set(SPLASHKIT_EASYLOGGING "${SPLASHKIT_ROOT}/coresdk/external/easyloggingpp")
set(SPLASHKIT_TESTS "${SPLASHKIT_ROOT}/coresdk/src/test")

# Pass as Macros to C++
target_compile_definitions(Transpiler PRIVATE
SPLASHKIT_CORESDK="${SPLASHKIT_CORESDK}"
SPLASHKIT_BACKEND="${SPLASHKIT_BACKEND}"
SPLASHKIT_CATCH="${SPLASHKIT_CATCH}"
SPLASHKIT_EASYLOGGING="${SPLASHKIT_EASYLOGGING}"
SPLASHKIT_TESTS="${SPLASHKIT_TESTS}"
)

# Output to /bin
set_target_properties(Transpiler PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${SPLASHKIT_ROOT}/bin
)

find_package(LLVM REQUIRED CONFIG) # Find the LLVM installation and use its CMake config
find_package(Clang REQUIRED CONFIG) # Find the clang installation and use its CMake config
find_package(nlohmann_json REQUIRED)

# Add the header paths from the config to the include dirs (PRIVATE so only this executable uses these paths)
target_include_directories(Transpiler PRIVATE
${LLVM_INCLUDE_DIRS}
${CLANG_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/external
${CMAKE_CURRENT_BINARY_DIR}
)

# Add the libraries
target_link_libraries(Transpiler PRIVATE
clangTooling
clangAST
clangFrontend
nlohmann_json::nlohmann_json
)
3 changes: 3 additions & 0 deletions tools/transpiler/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
rm -rf build
cmake -S . -B build
253 changes: 253 additions & 0 deletions tools/transpiler/src/ast.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#pragma once

#include <nlohmann/json.hpp>

using json = nlohmann::json;

struct Type
{
std::string name;
bool isConst = false;
bool isPointer = false;
std::vector<Type> templateArguments; // e.g. SomeType<TypeA, TypeB>

bool operator==(const Type& other) const
{
return name == other.name &&
isConst == other.isConst &&
isPointer == other.isPointer &&
templateArguments == other.templateArguments;
}

NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, name, isConst, isPointer, templateArguments)
};

struct Expression
{
Type type;

virtual ~Expression() = default; // Explicit destructor for safety (pointers in derived classes)
virtual void serialise(json &j) const = 0; // Virtual function for base classes to define how they should be serialised
};

// Defines how nlohmann/json converts this type (Expression) to json
inline void to_json(json &j, const Expression &e)
{
e.serialise(j);
}

// Defines how nlohmann/json converts this type (Unique pointer to Expression) to json
inline void to_json(json &j, const std::unique_ptr<Expression> &e)
{
if (e) e->serialise(j);
else j = nullptr; // This converts to "null" with nlohmann/json
}

struct LiteralExpression : Expression
{
std::string value;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(LiteralExpression, value, type)
// Convert the current object (the derived type) to json, and assign a "kind"
void serialise(json &j) const override { j = *this; j["kind"] = "LiteralExpression"; }
};

struct Argument
{
std::unique_ptr<Expression> expression;
bool isRef = false;

NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Argument, expression, isRef)
};

enum class ReferenceKind
{
Variable,
EnumConstant
};

struct ReferenceExpression : Expression
{
std::string name;
ReferenceKind refKind;
std::string parentEnumType; // Used for enum constants
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ReferenceExpression, type, name, refKind, type, parentEnumType)
void serialise(json &j) const override { j = *this; j["kind"] = "ReferenceExpression"; }
};

struct ConstructorExpression : Expression
{
std::vector<std::unique_ptr<Expression>> arguments;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ConstructorExpression, type, arguments)
void serialise(json &j) const override { j = *this; j["kind"] = "ConstructorExpression"; }
};

struct InitListExpression : Expression
{
std::vector<std::unique_ptr<Expression>> elements;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(InitListExpression, elements, type)
void serialise(json &j) const override { j = *this; j["kind"] = "InitListExpression"; }
};

struct CallExpression : Expression
{
std::string functionName;
std::string qualifiedFunctionName;
std::vector<Argument> arguments;
std::unique_ptr<Expression> parentObject; // nullptr if not a member call
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(CallExpression, type, functionName, qualifiedFunctionName, arguments, parentObject)
void serialise(json &j) const override { j = *this; j["kind"] = "CallExpression"; }
};

struct IndexExpression : Expression
{
std::unique_ptr<Expression> object;
std::unique_ptr<Expression> index;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(IndexExpression, type, object, index)
void serialise(json &j) const override { j = *this; j["kind"] = "IndexExpression"; }
};

struct BinaryExpression : Expression
{
std::string op;
std::unique_ptr<Expression> left;
std::unique_ptr<Expression> right;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(BinaryExpression, type, op, left, right)
void serialise(json &j) const override { j = *this; j["kind"] = "BinaryExpression"; }
};

struct UnaryExpression : Expression
{
std::string op;
std::unique_ptr<Expression> operand;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(UnaryExpression, type, op, operand)
void serialise(json &j) const override { j = *this; j["kind"] = "UnaryExpression"; }
};

struct MemberExpression : Expression
{
std::unique_ptr<Expression> parentObject;
std::string memberName;

NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(MemberExpression, type, parentObject, memberName)

void serialise(json &j) const override { j = *this; j["kind"] = "MemberExpression"; }
};

struct VariableDeclaration
{
std::string name;
Type type;
std::string declarationId;
std::unique_ptr<Expression> initializer;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(VariableDeclaration, name, type, declarationId, initializer)
};

struct Parameter
{
std::string name;
Type type;
std::unique_ptr<Expression> defaultValue;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Parameter, name, type, defaultValue)
};

struct Statement
{
virtual ~Statement() = default; // Explicit destructor for safety (pointers in derived classes)
virtual void serialise(json &j) const = 0; // Virtual function for base classes to define how they should be serialised
};

// Defines how nlohmann/json converts this type (Statement) to json
inline void to_json(json &j, const Statement &e)
{
e.serialise(j);
}

// Defines how nlohmann/json converts this type (Shared pointer to Statement) to json
inline void to_json(json &j, const std::shared_ptr<Statement> &e)
{
if (e) e->serialise(j);
else j = nullptr; // This converts to "null" with nlohmann/json
}

struct FunctionDeclaration
{
std::string name;
std::vector<Parameter> parameters;
std::vector<std::shared_ptr<Statement>> body;
Type returnType;
bool isGlobal;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(FunctionDeclaration, name, parameters, body, returnType, isGlobal)
};

struct ExpressionStatement : Statement
{
std::unique_ptr<Expression> expression;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ExpressionStatement, expression)
void serialise(json &j) const override { j = *this; j["kind"] = "ExpressionStatement"; }
};

struct VariableDeclarationStatement : Statement
{
VariableDeclaration variable;
bool isGlobal;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(VariableDeclarationStatement, variable, isGlobal)
void serialise(json &j) const override { j = *this; j["kind"] = "VariableDeclarationStatement"; }
};

struct ReturnStatement : Statement
{
std::unique_ptr<Expression> value;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ReturnStatement, value)
void serialise(json &j) const override { j = *this; j["kind"] = "ReturnStatement"; }
};

struct ForStatement : Statement
{
std::vector<std::shared_ptr<Statement>> initialiser;
std::unique_ptr<Expression> condition;
std::unique_ptr<Expression> increment;
std::vector<std::shared_ptr<Statement>> body;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ForStatement, initialiser, condition, increment, body)
void serialise(json &j) const override { j = *this; j["kind"] = "ForStatement"; }
};

enum class AssertionType
{
Require,
RequireFalse,
RequireThrows,
Check,
CheckFalse
};

struct AssertionStatement : Statement
{
AssertionType type;
std::unique_ptr<Expression> expression;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(AssertionStatement, type, expression)
void serialise(json &j) const override { j = *this; j["kind"] = "AssertionStatement"; }
};

struct Section
{
std::string name;
std::vector<std::shared_ptr<Statement>> body;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Section, name, body)
};

struct TestCase
{
std::string name;
std::vector<std::string> tags;
std::vector<Section> sections;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(TestCase, name, tags, sections)
};

struct CustomAST
{
std::string filename;
std::vector<VariableDeclarationStatement> globals;
std::vector<FunctionDeclaration> functions;
std::vector<TestCase> tests;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(CustomAST, filename, globals, functions, tests)
};
Loading