Skip to content

Commit e01f83c

Browse files
Implement C++ transpiler backend with C++20 coroutines
- CppCodeBuilder emits a C++ class per script deriving from CppExecutableCode, registered by the FNV-1a hash of its source - scripts and procedures are C++20 coroutines (CppCoroutine) - run the shared codegen tests with the cpp backend: codegen_test_generator collects the generated code which is compiled into codegen_test - add the codebase generator and scratchcpp-transpiler tool Co-Authored-By: zemanadam893@gmail.com <zemanadam893@gmail.com>
1 parent b07f9de commit e01f83c

23 files changed

Lines changed: 2017 additions & 78 deletions

‎CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ endif()
155155
# C++ transpiler
156156
if (LIBSCRATCHCPP_CODEGEN_BACKEND STREQUAL "cpp")
157157
target_compile_definitions(scratchcpp PRIVATE CODEGEN_BACKEND_CPP)
158+
add_subdirectory(transpiler)
158159
endif()
159160

160161
# Macros

‎src/engine/internal/codegen/cpp/CMakeLists.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ target_sources(scratchcpp
1212
cppconstant.h
1313
cppcodebuilder.cpp
1414
cppcodebuilder.h
15+
cppcoroutine.h
16+
cppprelude.h
17+
cppcodebasegenerator.cpp
18+
cppcodebasegenerator.h
1519
)
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#include <scratchcpp/project.h>
4+
#include <filesystem>
5+
#include <fstream>
6+
#include <sstream>
7+
#include <iostream>
8+
#include <iomanip>
9+
#include <map>
10+
#include <vector>
11+
12+
#include "cppcodebasegenerator.h"
13+
#include "cppcompilercontext.h"
14+
15+
using namespace libscratchcpp;
16+
17+
namespace
18+
{
19+
20+
bool writeFile(const std::filesystem::path &path, const std::string &content)
21+
{
22+
std::ofstream file(path, std::ios::binary);
23+
24+
if (!file.is_open()) {
25+
std::cerr << "error: failed to write " << path.string() << std::endl;
26+
return false;
27+
}
28+
29+
file << content;
30+
return true;
31+
}
32+
33+
} // namespace
34+
35+
/*!
36+
* Compiles the given project using the C++ backend and writes a CMake project to outputDir:
37+
* - project.cpp - the generated code of all scripts
38+
* - project_data.cpp - the embedded project file
39+
* - main.cpp - loads and runs the embedded project
40+
* - CMakeLists.txt
41+
*/
42+
bool CppCodebaseGenerator::generate(const std::string &projectFile, const std::string &outputDir, const std::string &projectName)
43+
{
44+
std::ifstream input(projectFile, std::ios::binary);
45+
46+
if (!input.is_open()) {
47+
std::cerr << "error: failed to read " << projectFile << std::endl;
48+
return false;
49+
}
50+
51+
std::stringstream data;
52+
data << input.rdbuf();
53+
54+
// The code builder reports the source of every compiled script
55+
std::map<uint64_t, std::string> sources;
56+
CppCompilerContext::setSourceCallback([&sources](uint64_t hash, const std::string &source) { sources[hash] = source; });
57+
58+
std::stringstream code;
59+
bool ret;
60+
61+
{
62+
Project project(projectFile);
63+
ret = project.load();
64+
}
65+
66+
CppCompilerContext::setSourceCallback(nullptr);
67+
68+
if (ret) {
69+
code << CppCompilerContext::sourceHeader();
70+
71+
for (const auto &[hash, source] : sources)
72+
code << source << "\n";
73+
}
74+
75+
if (!ret)
76+
return false;
77+
78+
std::filesystem::path dir(outputDir);
79+
std::error_code err;
80+
std::filesystem::create_directories(dir, err);
81+
82+
return writeFile(dir / "project.cpp", code.str()) && writeFile(dir / "project_data.cpp", dataSource(data.str())) && writeFile(dir / "main.cpp", mainSource()) &&
83+
writeFile(dir / "CMakeLists.txt", cmakeSource(projectName));
84+
}
85+
86+
std::string CppCodebaseGenerator::mainSource()
87+
{
88+
return R"(// Generated by libscratchcpp, do not edit
89+
90+
#include <scratchcpp/project.h>
91+
#include <filesystem>
92+
#include <fstream>
93+
#include <string>
94+
95+
extern const unsigned char scratchcpp_project_data[];
96+
extern const unsigned long long scratchcpp_project_size;
97+
98+
int main(int argc, char **argv)
99+
{
100+
// libscratchcpp loads projects from files, so the embedded project is extracted first
101+
std::filesystem::path path = std::filesystem::temp_directory_path() / ("scratchcpp_project_" + std::to_string(std::hash<std::string>()(argv[0])) + ".sb3");
102+
103+
{
104+
std::ofstream file(path, std::ios::binary);
105+
file.write(reinterpret_cast<const char *>(scratchcpp_project_data), scratchcpp_project_size);
106+
}
107+
108+
// The C++ backend finds the generated code of each script in project.cpp
109+
libscratchcpp::Project project(path.string());
110+
bool loaded = project.load();
111+
std::error_code err;
112+
std::filesystem::remove(path, err);
113+
114+
if (!loaded)
115+
return 1;
116+
117+
project.run();
118+
return 0;
119+
}
120+
)";
121+
}
122+
123+
std::string CppCodebaseGenerator::dataSource(const std::string &data)
124+
{
125+
std::stringstream ss;
126+
ss << "// Generated by libscratchcpp, do not edit\n\n";
127+
ss << "extern const unsigned long long scratchcpp_project_size = " << data.size() << "ULL;\n\n";
128+
ss << "extern const unsigned char scratchcpp_project_data[] = {";
129+
130+
for (size_t i = 0; i < data.size(); i++) {
131+
if (i % 16 == 0)
132+
ss << "\n ";
133+
134+
ss << " 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)(unsigned char)data[i] << ",";
135+
}
136+
137+
ss << std::dec << "\n 0x00\n};\n";
138+
return ss.str();
139+
}
140+
141+
std::string CppCodebaseGenerator::cmakeSource(const std::string &projectName)
142+
{
143+
std::string ret = R"(# Generated by libscratchcpp
144+
cmake_minimum_required(VERSION 3.14)
145+
project(@NAME@ LANGUAGES CXX)
146+
147+
set(CMAKE_CXX_STANDARD 20)
148+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
149+
150+
# Use either libscratchcpp sources (LIBSCRATCHCPP_SOURCE_DIR) or a prebuilt library (LIBSCRATCHCPP_INCLUDE_DIR and LIBSCRATCHCPP_LIBRARY)
151+
set(LIBSCRATCHCPP_SOURCE_DIR "" CACHE PATH "libscratchcpp source directory")
152+
set(LIBSCRATCHCPP_INCLUDE_DIR "" CACHE PATH "libscratchcpp include directory")
153+
set(LIBSCRATCHCPP_LIBRARY "" CACHE FILEPATH "libscratchcpp library")
154+
155+
add_executable(@NAME@ main.cpp project.cpp project_data.cpp)
156+
157+
if(LIBSCRATCHCPP_SOURCE_DIR)
158+
set(LIBSCRATCHCPP_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
159+
set(LIBSCRATCHCPP_CODEGEN_BACKEND "cpp" CACHE STRING "" FORCE)
160+
add_subdirectory(${LIBSCRATCHCPP_SOURCE_DIR} libscratchcpp)
161+
target_link_libraries(@NAME@ PRIVATE scratchcpp)
162+
elseif(LIBSCRATCHCPP_INCLUDE_DIR AND LIBSCRATCHCPP_LIBRARY)
163+
target_include_directories(@NAME@ PRIVATE ${LIBSCRATCHCPP_INCLUDE_DIR})
164+
target_link_libraries(@NAME@ PRIVATE ${LIBSCRATCHCPP_LIBRARY})
165+
else()
166+
message(FATAL_ERROR "Set LIBSCRATCHCPP_SOURCE_DIR or LIBSCRATCHCPP_INCLUDE_DIR and LIBSCRATCHCPP_LIBRARY")
167+
endif()
168+
)";
169+
std::string name = projectName;
170+
171+
for (char &c : name) {
172+
if (!std::isalnum((unsigned char)c) && c != '_' && c != '-')
173+
c = '_';
174+
}
175+
176+
size_t pos;
177+
178+
while ((pos = ret.find("@NAME@")) != std::string::npos)
179+
ret.replace(pos, 6, name);
180+
181+
return ret;
182+
}
183+
184+
extern "C"
185+
{
186+
LIBSCRATCHCPP_EXPORT bool cpp_generate_codebase(const char *projectFile, const char *outputDir, const char *projectName)
187+
{
188+
return CppCodebaseGenerator::generate(projectFile, outputDir, projectName);
189+
}
190+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#pragma once
4+
5+
#include <string>
6+
7+
#include "test_export.h"
8+
9+
namespace libscratchcpp
10+
{
11+
12+
/*! Generates a standalone C++ codebase (CMake project) from a Scratch project. */
13+
class LIBSCRATCHCPP_TEST_EXPORT CppCodebaseGenerator
14+
{
15+
public:
16+
static bool generate(const std::string &projectFile, const std::string &outputDir, const std::string &projectName = "project");
17+
18+
static std::string mainSource();
19+
static std::string dataSource(const std::string &data);
20+
static std::string cmakeSource(const std::string &projectName);
21+
};
22+
23+
} // namespace libscratchcpp

0 commit comments

Comments
 (0)