hoshi-lang dev
Yet another programming language
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include "share/defines.h"
5#include <compiler/ir/IR.h>
11#include <iostream>
12#include <llvm/Support/raw_ostream.h>
13#include <share/def.hpp>
14#include <stdexcept>
15#include <string>
16#include <vector>
17#include <filesystem>
18
19
20namespace fs = std::filesystem;
21
22
24 std::wstring platform) {
26 if (platform == L"windows") return ".exe";
27 return "";
28 } else if (type == yoi::IRBuildConfig::BuildType::library) {
29 if (platform == L"windows") return ".dll";
30 if (platform == L"darwin") return ".dylib";
31 return ".so";
32 }
33 return "";
34}
35
36
37void printUsage(const char* programName) {
38 std::cerr << "hoshi-lang compiler\n";
39 std::cout << "Made with love by Jerry Chou (This project is licensed under the MIT license.)\n";
40 std::cerr << "Usage: " << programName << " [options] <input_file> ...\n"
41 << "Options:\n"
42 << " -o <path>, --output <path> Set output file path (e.g., build/my_app).\n"
43 << " If <path> is a directory (ends with / or \\), input filename is used.\n"
44 << " If not specified, derived from input_file in the current directory.\n"
45 << " --build-type <type> Specify build type (executable, static-lib, shared-lib). Default: executable\n"
46 << " --build-mode <mode> Specify build mode (debug, release). Default: debug\n"
47 << " --linker <linker> Specify object linker (cc, cl, none). Default: cc (cl on Windows platform)\n"
48 << " 'none' will generate .o file but skip final linking.\n"
49 << " --clean, --remove-intermediate Remove intermediate files (.yoi, .ll, .o) after compilation.\n"
50 << " Default: do not preserve intermediate files.\n"
51 << " -I <path>, --include <path> Add an include directory to search for header files and dynamic libraries.\n"
52 << " -D <k> <v>, --define <k> <v> Add a macro definition.\n"
53 << " -W <key>, --warning <key> Enable warning for a specific category.\n"
54 << " -S <key>, --suppress <key> Suppress warning for a specific category.\n"
55 << " -E <key>, --error <key> Treat error for a specific category as a warning.\n"
56 << " -C <path>, --project-cache <path> Set project cache directory.\n"
57 << " --preserve-intermediate Explicitly preserve intermediate files.\n"
58 << " --linker-option <flag>, -L <flag> Pass an additional flag to the linker.\n"
59 << " This option can be used multiple times.\n"
60 << " --whereami, -w Print the path to the hoshi-lang installation directory.\n"
61 << " --build-number Print the build number of hoshi-lang.\n"
62 << " -h, --help Display this help message.\n";
63}
64
65int main(int argc, const char **argv) {
66
67
68 std::string inputFile;
69 std::string outputPathStr;
72 yoi::wstr projectCacheDir;
73 std::wstring targetPlatform = yoi::string2wstring(YOI_PLATFORM);
74 std::wstring targetArch = yoi::string2wstring(YOI_ARCH);
75 std::wstring targetTriple = L"";
77 yoi::vec<yoi::wstr> includeDirs{L"", (std::filesystem::path(yoi::whereIsHoshiLang()) / ".." / "lib").wstring()};
79 yoi::vec<yoi::wstr> additionalLinkerOptions;
81 bool preserveIntermediateFiles = false;
82
83 for (int i = 1; i < argc; ++i) {
84 std::string arg = argv[i];
85
86 if (arg == "-o" || arg == "--output") {
87 if (i + 1 < argc) {
88 outputPathStr = argv[++i];
89 } else {
90 std::cerr << "Error: " << arg << " requires a path argument.\n";
91 printUsage(argv[0]);
92 return 1;
93 }
94 } else if (arg == "--build-type") {
95 if (i + 1 < argc) {
96 std::string typeStr = argv[++i];
97 if (typeStr == "executable") buildType = yoi::IRBuildConfig::BuildType::executable;
98 else if (typeStr == "library") buildType = yoi::IRBuildConfig::BuildType::library;
99 else {
100 std::cerr << "Error: Invalid build type '" << typeStr << "'. Valid types: executable, library.\n";
101 printUsage(argv[0]);
102 return 1;
103 }
104 } else {
105 std::cerr << "Error: " << arg << " requires a type argument.\n";
106 printUsage(argv[0]);
107 return 1;
108 }
109 } else if (arg == "--build-mode") {
110 if (i + 1 < argc) {
111 std::string modeStr = argv[++i];
112 if (modeStr == "debug") buildMode = yoi::IRBuildConfig::BuildMode::debug;
113 else if (modeStr == "release") buildMode = yoi::IRBuildConfig::BuildMode::release;
114 else {
115 std::cerr << "Error: Invalid build mode '" << modeStr << "'. Valid modes: debug, release.\n";
116 printUsage(argv[0]);
117 return 1;
118 }
119 } else {
120 std::cerr << "Error: " << arg << " requires a mode argument.\n";
121 printUsage(argv[0]);
122 return 1;
123 }
124 } else if (arg == "--linker") {
125 if (i + 1 < argc) {
126 std::string linkerStr = argv[++i];
127 if (linkerStr == "cc") useObjectLinker = yoi::IRBuildConfig::UseObjectLinker::cc;
128 else if (linkerStr == "cl") useObjectLinker = yoi::IRBuildConfig::UseObjectLinker::cl;
129 else if (linkerStr == "none") useObjectLinker = yoi::IRBuildConfig::UseObjectLinker::none;
130 else {
131 std::cerr << "Error: Invalid linker type '" << linkerStr << "'. Valid types: cc, cl, none.\n";
132 printUsage(argv[0]);
133 return 1;
134 }
135 } else {
136 std::cerr << "Error: " << arg << " requires a linker argument.\n";
137 printUsage(argv[0]);
138 return 1;
139 }
140 } else if (arg == "--clean" || arg == "--remove-intermediate") {
141 preserveIntermediateFiles = false;
142 } else if (arg == "-C" || arg == "--project-cache") {
143 if (i + 1 < argc) {
144 projectCacheDir = yoi::string2wstring(argv[++i]);
145 } else {
146 std::cerr << "Error: " << arg << " requires a path argument.\n";
147 printUsage(argv[0]);
148 return 1;
149 }
150 } else if (arg == "-I" || arg == "--include") {
151 yoi::wstr includeDir = yoi::string2wstring(argv[++i]);
152 includeDirs.push_back(includeDir);
153 } else if (arg == "-t" || arg == "--target") {
154 targetTriple = yoi::string2wstring(argv[++i]);
155 } else if (arg == "--preserve-intermediate") {
156 preserveIntermediateFiles = true;
157 } else if (arg == "--linker-option" || arg == "-L") {
158 if (i + 1 < argc) {
159 additionalLinkerOptions.push_back(yoi::string2wstring(argv[++i]));
160 } else {
161 std::cerr << "Error: " << arg << " requires a flag argument.\n";
162 printUsage(argv[0]);
163 return 1;
164 }
165 } else if (arg == "--help" || arg == "-h") {
166 printUsage(argv[0]);
167 return 0;
168 } else if (arg == "--whereami" || arg == "-w") {
170 return 0;
171 } else if (arg == "--build-number") {
172 std::cout << HOSHI_LANG_VERSION << "\n";
173 return 0;
174 } else if (arg == "-D" || arg == "--define") {
175 if (i + 2 < argc) {
176 yoi::wstr key = yoi::string2wstring(argv[++i]);
177 yoi::wstr value = yoi::string2wstring(argv[++i]);
178 macroDefs.emplace_back(key, value);
179 } else {
180 std::cerr << "Error: " << arg << " requires two arguments.\n";
181 printUsage(argv[0]);
182 return 1;
183 }
184 } else if (arg == "-W" || arg == "--warning") {
185 if (i + 1 < argc) {
186 std::string key = argv[++i];
188 } else {
189 std::cerr << "Error: " << arg << " requires one arguments.\n";
190 printUsage(argv[0]);
191 return 1;
192 }
193 } else if (arg == "-S" || arg == "--suppress") {
194 if (i + 1 < argc) {
195 std::string key = argv[++i];
197 } else {
198 std::cerr << "Error: " << arg << " requires one arguments.\n";
199 printUsage(argv[0]);
200 return 1;
201 }
202 } else if (arg == "-E" || arg == "--error") {
203 if (i + 1 < argc) {
204 std::string key = argv[++i];
206 } else {
207 std::cerr << "Error: " << arg << " requires one arguments.\n";
208 printUsage(argv[0]);
209 return 1;
210 }
211 } else if (!arg.starts_with("-")) {
212 if (arg.ends_with(".hoshi") && inputFile.empty())
213 inputFile = arg;
214 else if (arg.ends_with(".hoshi") && !inputFile.empty()) {
215 std::cerr << "Warning: Multiple input files specified: " << inputFile << " and " << arg << "\n";
216 printUsage(argv[0]);
217 return 1;
218 }
219 else
220 additionalLinkingFiles.push_back(yoi::string2wstring(arg));
221 } else {
222 std::cerr << "Error: Unknown argument: " << arg << "\n";
223 printUsage(argv[0]);
224 return 1;
225 }
226 }
227
228
229 if (inputFile.empty()) {
230 std::cerr << "Error: No input file provided.\n";
231 printUsage(argv[0]);
232 return 1;
233 }
234 if (!fs::exists(inputFile)) {
235 std::cerr << "Error: Input file '" << inputFile << "' does not exist.\n";
236 return 1;
237 }
238 if (!fs::is_regular_file(inputFile)) {
239 std::cerr << "Error: Input file '" << inputFile << "' is not a regular file.\n";
240 return 1;
241 }
242
243
244 fs::path inputFilePath(inputFile);
245 fs::path outputDir;
246 fs::path outputBaseName;
247
248 if (outputPathStr.empty()) {
249
250 outputDir = fs::current_path();
251 outputBaseName = inputFilePath.stem();
252 } else {
253 fs::path specifiedOutputPath(outputPathStr);
254 if (specifiedOutputPath.has_filename()) {
255
256 outputDir = specifiedOutputPath.parent_path();
257 outputBaseName = specifiedOutputPath.stem();
258 } else {
259 outputDir = specifiedOutputPath;
260 outputBaseName = inputFilePath.stem();
261 }
262 }
263
264
265 try {
266 if (!outputDir.empty() && !fs::exists(outputDir)) {
267 fs::create_directories(outputDir);
268 }
269 } catch (const fs::filesystem_error& e) {
270 std::cerr << "Error: Could not create output directory '" << outputDir.string() << "': " << e.what() << "\n";
271 return 1;
272 }
273
274
275 fs::path yoiIRFile = outputDir / (outputBaseName.string() + ".yoi");
276 fs::path llvmIRFile = outputDir / (outputBaseName.string() + ".ll");
277 fs::path objectFile = outputDir / (outputBaseName.string() + ".o");
278 fs::path finalOutput = outputDir / (outputBaseName.string() + getOutputExtension(buildType, targetPlatform));
279
280
281 std::vector<fs::path> intermediateFilesToClean;
282 if (!preserveIntermediateFiles) {
283 intermediateFilesToClean.push_back(yoiIRFile);
284 intermediateFilesToClean.push_back(llvmIRFile);
285 intermediateFilesToClean.push_back(objectFile);
286 }
287
288 int exitCode = 0;
289
290
291 std::shared_ptr<yoi::compilerContext> compilerCtx =
292 std::make_shared<yoi::compilerContext>();
293 try {
294 compilerCtx->initializeSharedObjects();
295
296
297 compilerCtx->setBuildConfig(yoi::IRBuildConfig::Builder()
298 .setBuildType(buildType)
299 .setBuildPlatform(yoi::string2wstring(YOI_PLATFORM))
300 .setBuildMode(buildMode)
301 .setBuildArch(yoi::string2wstring(YOI_ARCH))
302 .setTargetTriple(L"")
303 .setUseObjectLinker(useObjectLinker)
304 .setPreserveIntermediateFiles(preserveIntermediateFiles)
305 .setImmediatelyClearupCache(projectCacheDir.empty())
306 .setSearchPaths(includeDirs)
307 .setBuildCachePath(projectCacheDir)
310 .setMarco(L"hoshi_feature_version", yoi::string2wstring(HOSHI_LANG_VERSION))
312 .setAdditionalLinkingFiles(additionalLinkingFiles)
313 .setAdditionalLinkerOptions(additionalLinkerOptions)
314 .yield());
315
316 for (auto &macro : macroDefs) {
317 compilerCtx->getBuildConfig()->marcos[macro.first] = macro.second;
318 }
319
320 yoi::wstr input = yoi::string2wstring(inputFile);
321
322 auto entryModuleId = compilerCtx->compileModule(input);
323 compilerCtx->runOptimizer();
324
325 std::cout << "Linking Yoi IR modules...\n";
326 yoi::IRLinker linker;
327 auto objectIRFile = linker.link(compilerCtx, entryModuleId);
328 compilerCtx->setIRObjectFile(objectIRFile);
329 auto unifiedModule = objectIRFile->compiledModule;
330
331 auto yoiIRStr = unifiedModule->to_string();
332 std::error_code ec_yoi;
333 llvm::raw_fd_stream yoi_file(yoiIRFile.string(), ec_yoi);
334 if (ec_yoi) {
335 throw std::runtime_error("Could not open YOI IR output file '" + yoiIRFile.string() + "': " + ec_yoi.message());
336 }
337 yoi_file << yoi::wstring2string(yoiIRStr);
338 yoi_file.close();
339
340 yoi::LLVMCodegen llvmCodegen(compilerCtx, unifiedModule);
341 std::cout << "Generating target object files...\n";
342
343 yoi::vec<yoi::wstr> objectFileNames;
344
345 TIMER("Generating target object files", objectFileNames = llvmCodegen.generate());
346
347 if (preserveIntermediateFiles) {
348 // For debug verification, we print the IR of the main input module
349 llvmCodegen.dumpIR(L"builtin", llvmIRFile.string());
350 }
351
352 if (useObjectLinker != yoi::IRBuildConfig::UseObjectLinker::none) {
353 yoi::ObjectLinker *objectLinker = nullptr;
354 switch (useObjectLinker) {
356 objectLinker = new yoi::ccObjectLinker(objectFileNames, compilerCtx->getBuildConfig());
357 break;
358 }
360 objectLinker = new yoi::clObjectLinker(objectFileNames, compilerCtx->getBuildConfig());
361 break;
362 }
363 default:
364 break;
365 }
366
367 if (objectLinker) {
368 std::cout << "Linking final " << yoi::wstring2string(targetPlatform) << " "
369 << (buildType == yoi::IRBuildConfig::BuildType::executable ? "executable" : "library")
370 << "...\n";
371 objectLinker->searchAndSetupLinker();
373 objectLinker->link(yoi::string2wstring(finalOutput.string()));
374 delete objectLinker;
375 }
376 } else {
377 std::cout << "Object linking skipped (--linker none).\n";
378 }
379 std::cout << "Compilation successful!\n";
380
381 if (compilerCtx->getBuildConfig()->immediatelyClearupCache) {
382 std::error_code ec_remove;
383 std::filesystem::remove_all(compilerCtx->getBuildConfig()->buildCachePath, ec_remove);
384 if (ec_remove) {
385 std::cerr << "Warning: Could not remove cache file '" << yoi::wstring2string(compilerCtx->getBuildConfig()->buildCachePath) << "': " << ec_remove.message() << "\n";
386 }
387 }
388 } catch (const std::runtime_error &e) {
389 std::cerr << "Error: " << e.what() << std::endl;
390 exitCode = 1;
391 } /* catch (const std::exception& e) {
392 std::cerr << "An unexpected error occurred: " << e.what() << std::endl;
393 exitCode = 1;
394 } */
395
396 if (!preserveIntermediateFiles && exitCode == 0) {
397 for (const auto& file : intermediateFilesToClean) {
398 std::error_code ec_remove;
399 fs::remove(file, ec_remove);
400 if (ec_remove) {
401 std::cerr << "Warning: Could not remove intermediate file '" << file.string() << "': " << ec_remove.message() << "\n";
402 }
403 }
404 } else if (preserveIntermediateFiles) {
405 std::cout << "Intermediate files preserved.\n";
406 }
407
408 return exitCode;
409}
std::shared_ptr< IRObjectFile > link(const std::shared_ptr< compilerContext > &context, indexT entryModuleId)
Links all compiled modules from the context into a single IRObjectFile.
Definition IRLinker.cpp:18
yoi::vec< yoi::wstr > generate()
void dumpIR(const yoi::wstr &modulePath, const std::string &filename)
static yoi::vec< yoi::wstr > defaultAdditionalLinkingFiles()
ObjectLinker & setElysiaRuntimePath(const yoi::wstr &elysiaRuntimePath)
virtual ObjectLinker & link(const yoi::wstr &outputPath)=0
virtual ObjectLinker & searchAndSetupLinker()=0
#define YOI_PLATFORM
Definition def.hpp:31
#define YOI_ARCH
Definition def.hpp:44
#define HOSHI_LANG_VERSION
Definition defines.h:4
#define HOSHI_LANG_GIT_COMMIT_HASH
Definition defines.h:5
#define TIMER(X, Y)
int main()
Definition loop.cpp:3
std::string getOutputExtension(yoi::IRBuildConfig::BuildType type, std::wstring platform)
Definition main.cpp:23
void printUsage(const char *programName)
Definition main.cpp:37
std::string wstring2string(const std::wstring &v)
Definition def.cpp:230
std::vector< t > vec
Definition def.hpp:56
std::wstring string2wstring(const std::string &v)
Definition def.cpp:224
yoi::wstr realpath(const std::wstring &path)
Definition def.cpp:236
std::wstring whereIsHoshiLang()
Definition def.cpp:246
std::wstring wstr
Definition def.hpp:51
std::map< std::string, ExceptionHandleType > exception_categories
Definition def.cpp:27
Builder & setBuildCachePath(const yoi::wstr &buildCachePath)
Definition IR.cpp:1620
std::shared_ptr< IRBuildConfig > yield()
Definition IR.cpp:905
Builder & setAdditionalLinkingFiles(const yoi::vec< yoi::wstr > &additionalLinkingFiles)
Definition IR.cpp:1548
Builder & setAdditionalLinkerOptions(const yoi::vec< yoi::wstr > &additionalLinkerOptions)
Definition IR.cpp:1553
Builder & setSearchPaths(const yoi::vec< yoi::wstr > &searchPaths)
Definition IR.cpp:1062
Builder & setMarco(const yoi::wstr &name, const yoi::wstr &value)
Definition IR.cpp:1478