generator.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. 
  2. #include "generator.h"
  3. #include "../../../DFPSR/api/timeAPI.h"
  4. using namespace dsr;
  5. // Keep track of the current path, so that it only changes when needed.
  6. String previousPath;
  7. template <bool GENERATE>
  8. static void produce_printMessage(String &generatedCode, ScriptLanguage language, const ReadableString message) {
  9. if (GENERATE) {
  10. if (language == ScriptLanguage::Batch) {
  11. string_append(generatedCode, U"echo ", message, U"\n");
  12. } else if (language == ScriptLanguage::Bash) {
  13. string_append(generatedCode, U"echo ", message, U"\n");
  14. }
  15. } else {
  16. printText(message, U"\n");
  17. }
  18. }
  19. template <bool GENERATE>
  20. static void produce_setCompilationFolder(String &generatedCode, ScriptLanguage language, const ReadableString &newPath) {
  21. if (GENERATE) {
  22. if (!string_match(previousPath, newPath)) {
  23. if (string_length(previousPath) > 0) {
  24. if (language == ScriptLanguage::Batch) {
  25. string_append(generatedCode, "popd\n");
  26. } else if (language == ScriptLanguage::Bash) {
  27. string_append(generatedCode, U")\n");
  28. }
  29. }
  30. if (string_length(newPath) > 0) {
  31. if (language == ScriptLanguage::Batch) {
  32. string_append(generatedCode, "pushd ", newPath, "\n");
  33. } else if (language == ScriptLanguage::Bash) {
  34. string_append(generatedCode, U"(cd ", newPath, ";\n");
  35. }
  36. }
  37. }
  38. previousPath = newPath;
  39. } else {
  40. if (string_length(newPath) > 0) {
  41. if (string_length(previousPath) == 0) {
  42. previousPath = file_getCurrentPath();
  43. }
  44. file_setCurrentPath(newPath);
  45. }
  46. }
  47. }
  48. template <bool GENERATE>
  49. static void produce_resetCompilationFolder(String &generatedCode, ScriptLanguage language) {
  50. if (GENERATE) {
  51. produce_setCompilationFolder<true>(generatedCode, language, U"");
  52. } else {
  53. if (string_length(previousPath) > 0) {
  54. file_setCurrentPath(previousPath);
  55. }
  56. }
  57. }
  58. static bool waitForProcess(const DsrProcess &process) {
  59. while (true) {
  60. DsrProcessStatus status = process_getStatus(process);
  61. if (status == DsrProcessStatus::Completed) {
  62. return true;
  63. } else if (status == DsrProcessStatus::Crashed) {
  64. return false;
  65. }
  66. time_sleepSeconds(0.001);
  67. }
  68. }
  69. template <bool GENERATE>
  70. static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath, const List<String> &arguments) {
  71. if (GENERATE) {
  72. string_append(generatedCode, programPath);
  73. for (int64_t a = 0; a < arguments.length(); a++) {
  74. // TODO: Check if arguments contain spaces. In batch, adding quote marks might actually send the quote marks as a part of a string, which makes it complicated when default folder names on Windows contain spaces.
  75. string_append(generatedCode, U" ", arguments[a]);
  76. }
  77. string_append(generatedCode, U"\n");
  78. } else {
  79. // Print each external call in the terminal, because there is no script to inspect when not generating.
  80. if (arguments.length() > 0) {
  81. printText(U"Calling ", programPath, U" with");
  82. for (int64_t a = 0; a < arguments.length(); a++) {
  83. printText(U" ", arguments[a]);
  84. }
  85. printText(U"\n");
  86. } else {
  87. printText(U"Calling ", programPath, U"\n");
  88. }
  89. // TODO: How can multiple calls be made to the compiler at the same time and only wait for all before linking?
  90. // Don't want to break control flow from the code generating a serial script, so maybe a waitForAll command before performing any linking.
  91. // Don't want error messages from multiple failed compilations to collide in the same terminal.
  92. if (!waitForProcess(process_execute(programPath, arguments))) {
  93. printText(U"Failed to execute ", programPath, U"!\n");
  94. exit(0);
  95. }
  96. }
  97. }
  98. template <bool GENERATE>
  99. static void produce_callProgram(String &generatedCode, ScriptLanguage language, const ReadableString &programPath) {
  100. produce_callProgram<GENERATE>(generatedCode, language, programPath, List<String>());
  101. }
  102. template <bool GENERATE>
  103. void produce(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) {
  104. String generatedCode;
  105. if (GENERATE) {
  106. printText(U"Generating build script\n");
  107. if (language == ScriptLanguage::Batch) {
  108. string_append(generatedCode, U"@echo off\n\n");
  109. } else if (language == ScriptLanguage::Bash) {
  110. string_append(generatedCode, U"#!/bin/bash\n\n");
  111. }
  112. }
  113. // Generate code for compiling source code into objects.
  114. printText(U"Compiling ", input.sourceObjects.length(), U" objects.\n");
  115. for (int64_t o = 0; o < input.sourceObjects.length(); o++) {
  116. SourceObject *sourceObject = &(input.sourceObjects[o]);
  117. printText(U"\t* ", sourceObject->sourcePath, U"\n");
  118. produce_setCompilationFolder<GENERATE>(generatedCode, language, sourceObject->compileFrom);
  119. List<String> compilationArguments;
  120. for (int64_t i = 0; i < sourceObject->compilerFlags.length(); i++) {
  121. compilationArguments.push(sourceObject->compilerFlags[i]);
  122. }
  123. compilationArguments.push(U"-c");
  124. compilationArguments.push(sourceObject->sourcePath);
  125. compilationArguments.push(U"-o");
  126. compilationArguments.push(sourceObject->objectPath);
  127. if (GENERATE) {
  128. if (language == ScriptLanguage::Batch) {
  129. string_append(generatedCode, U"if exist ", sourceObject->objectPath, U" (\n");
  130. } else if (language == ScriptLanguage::Bash) {
  131. string_append(generatedCode, U"if [ -e \"", sourceObject->objectPath, U"\" ]; then\n");
  132. }
  133. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  134. if (language == ScriptLanguage::Batch) {
  135. string_append(generatedCode, U") else (\n");
  136. } else if (language == ScriptLanguage::Bash) {
  137. string_append(generatedCode, U"else\n");
  138. }
  139. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  140. produce_callProgram<GENERATE>(generatedCode, language, sourceObject->compilerName, compilationArguments);
  141. if (language == ScriptLanguage::Batch) {
  142. string_append(generatedCode, ")\n");
  143. } else if (language == ScriptLanguage::Bash) {
  144. string_append(generatedCode, U"fi\n");
  145. }
  146. } else {
  147. if (file_getEntryType(sourceObject->objectPath) == EntryType::File) {
  148. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Reusing ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  149. } else {
  150. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Compiling ", sourceObject->sourcePath, U" ID:", sourceObject->identityChecksum, U"."));
  151. produce_callProgram<GENERATE>(generatedCode, language, sourceObject->compilerName, compilationArguments);
  152. }
  153. }
  154. }
  155. // Generate code for linking objects into executables.
  156. printText(U"Linking ", input.linkerSteps.length(), U" executables:\n");
  157. for (int64_t l = 0; l < input.linkerSteps.length(); l++) {
  158. LinkingStep *linkingStep = &(input.linkerSteps[l]);
  159. String programPath = linkingStep->binaryName;
  160. printText(U"\tLinking ", programPath, U" of :\n");
  161. produce_setCompilationFolder<GENERATE>(generatedCode, language, linkingStep->compileFrom);
  162. List<String> linkerArguments;
  163. // Generate a list of object paths from indices.
  164. String allObjects;
  165. for (int64_t i = 0; i < linkingStep->sourceObjectIndices.length(); i++) {
  166. int64_t objectIndex = linkingStep->sourceObjectIndices[i];
  167. SourceObject *sourceObject = &(input.sourceObjects[objectIndex]);
  168. if (objectIndex >= 0 || objectIndex < input.sourceObjects.length()) {
  169. printText(U"\t\t* ", sourceObject->sourcePath, U"\n");
  170. string_append(allObjects, U" ", sourceObject->objectPath);
  171. linkerArguments.push(sourceObject->objectPath);
  172. } else {
  173. throwError(U"Object index ", objectIndex, U" is out of bound ", 0, U"..", (input.sourceObjects.length() - 1), U"\n");
  174. }
  175. }
  176. String linkerFlags;
  177. for (int64_t l = 0; l < linkingStep->linkerFlags.length(); l++) {
  178. String linkerFlag = linkingStep->linkerFlags[l];
  179. string_append(linkerFlags, " ", linkerFlag);
  180. linkerArguments.push(linkerFlag);
  181. printText(U"\t\t* ", linkerFlag, U" library\n");
  182. }
  183. linkerArguments.push(U"-o");
  184. linkerArguments.push(programPath);
  185. // Generate the code for building.
  186. if (string_length(linkerFlags) > 0) {
  187. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Linking ", programPath, U" with", linkerFlags, U"."));
  188. } else {
  189. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Linking ", programPath, U"."));
  190. }
  191. produce_callProgram<GENERATE>(generatedCode, language, linkingStep->compilerName, linkerArguments);
  192. if (linkingStep->executeResult) {
  193. produce_printMessage<GENERATE>(generatedCode, language, string_combine(U"Starting ", programPath));
  194. produce_callProgram<GENERATE>(generatedCode, language, programPath, List<String>());
  195. produce_printMessage<GENERATE>(generatedCode, language, U"The program terminated.");
  196. }
  197. }
  198. produce_resetCompilationFolder<GENERATE>(generatedCode, language);
  199. produce_printMessage<GENERATE>(generatedCode, language, U"Done building.");
  200. if (GENERATE) {
  201. printText(U"Saving script to ", scriptPath, "\n");
  202. if (language == ScriptLanguage::Batch) {
  203. string_save(scriptPath, generatedCode);
  204. } else if (language == ScriptLanguage::Bash) {
  205. string_save(scriptPath, generatedCode, CharacterEncoding::BOM_UTF8, LineEncoding::Lf);
  206. }
  207. }
  208. }
  209. void generateCompilationScript(SessionContext &input, const ReadableString &scriptPath, ScriptLanguage language) {
  210. produce<true>(input, scriptPath, language);
  211. }
  212. void executeBuildInstructions(SessionContext &input) {
  213. produce<false>(input, U"", ScriptLanguage::Unknown);
  214. }