Machine.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. 
  2. #include "Machine.h"
  3. #include "generator.h"
  4. #include "expression.h"
  5. #include "../../DFPSR/api/fileAPI.h"
  6. using namespace dsr;
  7. #define STRING_EXPR(FIRST_TOKEN, LAST_TOKEN) evaluateExpression(target, tokens, FIRST_TOKEN, LAST_TOKEN)
  8. #define INTEGER_EXPR(FIRST_TOKEN, LAST_TOKEN) expression_interpretAsInteger(STRING_EXPR(FIRST_TOKEN, LAST_TOKEN))
  9. #define PATH_EXPR(FIRST_TOKEN, LAST_TOKEN) file_getTheoreticalAbsolutePath(STRING_EXPR(FIRST_TOKEN, LAST_TOKEN), fromPath)
  10. Extension extensionFromString(const ReadableString& extensionName) {
  11. String upperName = string_upperCase(string_removeOuterWhiteSpace(extensionName));
  12. Extension result = Extension::Unknown;
  13. if (string_match(upperName, U"H")) {
  14. result = Extension::H;
  15. } else if (string_match(upperName, U"HPP")) {
  16. result = Extension::Hpp;
  17. } else if (string_match(upperName, U"C")) {
  18. result = Extension::C;
  19. } else if (string_match(upperName, U"CPP")) {
  20. result = Extension::Cpp;
  21. }
  22. return result;
  23. }
  24. int64_t findFlag(const Machine &target, const dsr::ReadableString &key) {
  25. for (int64_t f = 0; f < target.variables.length(); f++) {
  26. if (string_caseInsensitiveMatch(key, target.variables[f].key)) {
  27. return f;
  28. }
  29. }
  30. return -1;
  31. }
  32. ReadableString getFlag(const Machine &target, const dsr::ReadableString &key, const dsr::ReadableString &defaultValue) {
  33. int64_t existingIndex = findFlag(target, key);
  34. if (existingIndex == -1) {
  35. return defaultValue;
  36. } else {
  37. return target.variables[existingIndex].value;
  38. }
  39. }
  40. int64_t getFlagAsInteger(const Machine &target, const dsr::ReadableString &key, int64_t defaultValue) {
  41. int64_t existingIndex = findFlag(target, key);
  42. if (existingIndex == -1) {
  43. return defaultValue;
  44. } else {
  45. return string_toInteger(target.variables[existingIndex].value);
  46. }
  47. }
  48. void assignValue(Machine &target, const dsr::ReadableString &key, const dsr::ReadableString &value, bool inherited) {
  49. int64_t existingIndex = findFlag(target, key);
  50. if (existingIndex == -1) {
  51. target.variables.pushConstruct(string_upperCase(key), expression_unwrapIfNeeded(value), inherited);
  52. } else {
  53. target.variables[existingIndex].value = expression_unwrapIfNeeded(value);
  54. if (inherited) {
  55. target.variables[existingIndex].inherited = true;
  56. }
  57. }
  58. }
  59. static void flushToken(List<String> &targetTokens, String &currentToken) {
  60. if (string_length(currentToken) > 0) {
  61. targetTokens.push(currentToken);
  62. currentToken = U"";
  63. }
  64. }
  65. static String evaluateExpression(Machine &target, List<String> &tokens, int64_t startTokenIndex, int64_t endTokenIndex) {
  66. return expression_evaluate(tokens, startTokenIndex, endTokenIndex, [&target](ReadableString identifier) -> String {
  67. return getFlag(target, identifier, U"");
  68. });
  69. }
  70. static void crawlSource(ProjectContext &context, const dsr::ReadableString &absolutePath) {
  71. EntryType pathType = file_getEntryType(absolutePath);
  72. if (pathType == EntryType::File) {
  73. printText(U"Crawling for source from ", absolutePath, U".\n");
  74. analyzeFromFile(context, absolutePath);
  75. } else if (pathType == EntryType::Folder) {
  76. printText(U"Crawling was given the folder ", absolutePath, U" but a source file was expected!\n");
  77. } else if (pathType == EntryType::SymbolicLink) {
  78. // Symbolic links can point to both files and folder, so we need to follow it and find out what it really is.
  79. crawlSource(context, file_followSymbolicLink(absolutePath));
  80. }
  81. }
  82. // Copy inherited variables from parent to child.
  83. static void inheritMachine(Machine &child, const Machine &parent) {
  84. for (int v = 0; v < parent.variables.length(); v++) {
  85. String key = string_upperCase(parent.variables[v].key);
  86. if (parent.variables[v].inherited) {
  87. child.variables.push(parent.variables[v]);
  88. }
  89. }
  90. }
  91. static void interpretLine(ScriptTarget &output, ProjectContext &context, Machine &target, List<String> &tokens, const dsr::ReadableString &fromPath) {
  92. if (tokens.length() > 0) {
  93. bool activeLine = target.activeStackDepth >= target.currentStackDepth;
  94. /*
  95. printText(activeLine ? U"interpret:" : U"ignore:");
  96. for (int t = 0; t < tokens.length(); t++) {
  97. printText(U" [", tokens[t], U"]");
  98. }
  99. printText(U"\n");
  100. */
  101. ReadableString first = expression_getToken(tokens, 0);
  102. ReadableString second = expression_getToken(tokens, 1);
  103. if (activeLine) {
  104. // TODO: Implement elseif and else cases using a list as a virtual stack,
  105. // to remember at which layer the else cases have already been consumed by a true evaluation.
  106. // TODO: Remember at which depth the script entered, so that importing something can't leave the rest inside of a dangling if or else by accident.
  107. if (string_caseInsensitiveMatch(first, U"import")) {
  108. // Get path relative to importing script's path.
  109. String importPath = PATH_EXPR(1, tokens.length() - 1);
  110. evaluateScript(output, context, target, importPath);
  111. if (tokens.length() > 2) { printText(U"Unused tokens after import!\n");}
  112. } else if (string_caseInsensitiveMatch(first, U"if")) {
  113. // Being if statement
  114. bool active = INTEGER_EXPR(1, tokens.length() - 1);
  115. if (active) {
  116. target.activeStackDepth++;
  117. }
  118. target.currentStackDepth++;
  119. } else if (string_caseInsensitiveMatch(first, U"end") && string_caseInsensitiveMatch(second, U"if")) {
  120. // End if statement
  121. target.currentStackDepth--;
  122. target.activeStackDepth = target.currentStackDepth;
  123. } else if (string_caseInsensitiveMatch(first, U"crawl")) {
  124. // The right hand expression is evaluated into a path relative to the build script and used as the root for searching for source code.
  125. crawlSource(context, PATH_EXPR(1, tokens.length() - 1));
  126. } else if (string_caseInsensitiveMatch(first, U"build")) {
  127. // Build one or more other projects from a project file or folder path, as dependencies.
  128. // Having the same external project built twice during the same session is not allowed.
  129. Machine childTarget;
  130. inheritMachine(childTarget, target);
  131. String projectPath = file_getTheoreticalAbsolutePath(expression_unwrapIfNeeded(second), fromPath); // Use the second token as the folder path.
  132. argumentsToSettings(childTarget, tokens, 2); // Send all tokens after the second token as input arguments to buildProjects.
  133. printText("Building ", second, " from ", fromPath, " which is ", projectPath, "\n");
  134. build(output, projectPath, childTarget);
  135. } else if (string_caseInsensitiveMatch(first, U"link")) {
  136. // Only the path name itself is needed, so any redundant -l prefixes will be stripped away.
  137. String libraryName = STRING_EXPR(1, tokens.length() - 1);
  138. if (libraryName[0] == U'-' && (libraryName[1] == U'l' || libraryName[1] == U'L')) {
  139. libraryName = string_after(libraryName, 2);
  140. }
  141. target.linkerFlags.push(libraryName);
  142. } else if (string_caseInsensitiveMatch(first, U"compilerflag")) {
  143. target.compilerFlags.push(STRING_EXPR(1, tokens.length() - 1));
  144. } else if (string_caseInsensitiveMatch(first, U"message")) {
  145. // Print a message while evaluating the build script.
  146. // This is not done while actually compiling, so it will not know if compilation and linking worked or not.
  147. printText(STRING_EXPR(1, tokens.length() - 1));
  148. } else {
  149. if (tokens.length() == 1) {
  150. // Mentioning an identifier without assigning anything will assign it to one as a boolean flag.
  151. assignValue(target, first, U"1", false);
  152. } else if (string_match(second, U"=")) {
  153. // TODO: Create in-place math and string operations with different types of assignments.
  154. // Maybe use a different syntax beginning with a keyword?
  155. // TODO: Look for the assignment operator dynamically if references to collection elements are allowed as l-value expressions.
  156. // Using an equality sign replaces any previous value of the variable.
  157. assignValue(target, first, STRING_EXPR(2, tokens.length() - 1), false);
  158. } else {
  159. // TODO: Give better error messages.
  160. printText(U" Ignored unrecognized statement!\n");
  161. }
  162. }
  163. } else {
  164. if (string_caseInsensitiveMatch(first, U"if")) {
  165. target.currentStackDepth++;
  166. } else if (string_caseInsensitiveMatch(first, U"end") && string_caseInsensitiveMatch(second, U"if")) {
  167. target.currentStackDepth--;
  168. }
  169. }
  170. }
  171. tokens.clear();
  172. }
  173. void evaluateScript(ScriptTarget &output, ProjectContext &context, Machine &target, const ReadableString &scriptPath) {
  174. if (file_getEntryType(scriptPath) != EntryType::File) {
  175. printText(U"The script path ", scriptPath, U" does not exist!\n");
  176. }
  177. String projectContent = string_load(scriptPath);
  178. // Each new script being imported will have its own simulated current path for accessing files and such.
  179. String projectFolderPath = file_getAbsoluteParentFolder(scriptPath);
  180. String currentToken;
  181. List<String> currentLine; // Keep it fast and simple by only remembering tokens for the current line.
  182. bool quoted = false;
  183. bool commented = false;
  184. for (int i = 0; i <= string_length(projectContent); i++) {
  185. DsrChar c = projectContent[i];
  186. // The null terminator does not really exist in projectContent,
  187. // but dsr::String returns a null character safely when requesting a character out of bound,
  188. // which allow interpreting the last line without duplicating code.
  189. if (c == U'\n' || c == U'\0') {
  190. // Comment removing everything else.
  191. flushToken(currentLine, currentToken);
  192. interpretLine(output, context, target, currentLine, projectFolderPath);
  193. commented = false; // Automatically end comments at end of line.
  194. quoted = false; // Automatically end quotes at end of line.
  195. } else if (c == U'\"') {
  196. quoted = !quoted;
  197. string_appendChar(currentToken, c);
  198. } else if (c == U'#') {
  199. // Comment removing everything else until a new line comes.
  200. flushToken(currentLine, currentToken);
  201. interpretLine(output, context, target, currentLine, projectFolderPath);
  202. commented = true;
  203. } else if (!commented) {
  204. if (quoted) {
  205. // Insert character into quote.
  206. string_appendChar(currentToken, c);
  207. } else {
  208. if (c == U'(' || c == U')' || c == U'[' || c == U']' || c == U'{' || c == U'}' || c == U'=') {
  209. // Atomic token of a single character
  210. flushToken(currentLine, currentToken);
  211. string_appendChar(currentToken, c);
  212. flushToken(currentLine, currentToken);
  213. } else if (c == U' ' || c == U'\t') {
  214. // Whitespace
  215. flushToken(currentLine, currentToken);
  216. } else {
  217. // Insert unquoted character into token.
  218. string_appendChar(currentToken, c);
  219. }
  220. }
  221. }
  222. }
  223. }
  224. static List<String> initializedProjects;
  225. // Using a project file path and input arguments.
  226. void buildProject(ScriptTarget &output, const ReadableString &projectFilePath, Machine settings) {
  227. printText("Building project at ", projectFilePath, "\n");
  228. // Check if this project has begun building previously during this session.
  229. String absolutePath = file_getAbsolutePath(projectFilePath);
  230. for (int p = 0; p < initializedProjects.length(); p++) {
  231. if (string_caseInsensitiveMatch(absolutePath, initializedProjects[p])) {
  232. throwError(U"Found duplicate requests to build from the same initial script ", absolutePath, U" which could cause non-determinism if different arguments are given to each!\n");
  233. return;
  234. }
  235. }
  236. // Remember that building of this project has started.
  237. initializedProjects.push(absolutePath);
  238. // Evaluate compiler settings while searching for source code mentioned in the project and imported headers.
  239. printText(U"Executing project file from ", projectFilePath, U".\n");
  240. ProjectContext context;
  241. evaluateScript(output, context, settings, projectFilePath);
  242. // Find out where things are located.
  243. String projectPath = file_getAbsoluteParentFolder(projectFilePath);
  244. // Interpret ProgramPath relative to the project path.
  245. String fullProgramPath = getFlag(settings, U"ProgramPath", U"program");
  246. if (output.language == ScriptLanguage::Batch) {
  247. string_append(fullProgramPath, U".exe");
  248. }
  249. fullProgramPath = file_getTheoreticalAbsolutePath(fullProgramPath, projectPath);
  250. // If the SkipIfBinaryExists flag is given, we will abort as soon as we have handled its external BuildProjects requests and confirmed that the application exists.
  251. if (getFlagAsInteger(settings, U"SkipIfBinaryExists") && file_getEntryType(fullProgramPath) == EntryType::File) {
  252. // SkipIfBinaryExists was active and the binary exists, so abort here to avoid redundant work.
  253. printText(U"Skipping build of ", projectFilePath, U" because the SkipIfBinaryExists flag was given and ", fullProgramPath, U" was found.\n");
  254. return;
  255. }
  256. // Once we are done finding all source files, we can resolve the dependencies to create a graph connected by indices.
  257. resolveDependencies(context);
  258. if (getFlagAsInteger(settings, U"ListDependencies")) {
  259. printDependencies(context);
  260. }
  261. generateCompilationScript(output, context, settings, fullProgramPath);
  262. }
  263. // Using a folder path and input arguments for all projects.
  264. void buildProjects(ScriptTarget &output, const ReadableString &projectFolderPath, Machine &settings) {
  265. printText("Building all projects in ", projectFolderPath, "\n");
  266. file_getFolderContent(projectFolderPath, [&settings, &output](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
  267. if (entryType == EntryType::Folder) {
  268. buildProjects(output, entryPath, settings);
  269. } else if (entryType == EntryType::File) {
  270. ReadableString extension = string_upperCase(file_getExtension(entryName));
  271. if (string_match(extension, U"DSRPROJ")) {
  272. buildProject(output, entryPath, settings);
  273. }
  274. }
  275. });
  276. }
  277. void build(ScriptTarget &output, const ReadableString &projectPath, Machine &settings) {
  278. EntryType entryType = file_getEntryType(projectPath);
  279. printText("Building anything at ", projectPath, " which is ", entryType, "\n");
  280. if (entryType == EntryType::File) {
  281. String extension = string_upperCase(file_getExtension(projectPath));
  282. if (!string_match(extension, U"DSRPROJ")) {
  283. printText(U"Can't use the Build keyword with a file that is not a project!\n");
  284. } else {
  285. // Build the given project
  286. buildProject(output, projectPath, settings);
  287. }
  288. } else if (entryType == EntryType::Folder) {
  289. buildProjects(output, projectPath, settings);
  290. }
  291. }
  292. void argumentsToSettings(Machine &settings, const List<String> &arguments, int64_t firstArgument) {
  293. for (int a = firstArgument; a < arguments.length(); a++) {
  294. String argument = arguments[a];
  295. int64_t assignmentIndex = string_findFirst(argument, U'=');
  296. if (assignmentIndex == -1) {
  297. assignValue(settings, argument, U"1", true);
  298. printText(U"Assigning ", argument, U" to 1 from input argument.\n");
  299. } else {
  300. String key = string_removeOuterWhiteSpace(string_before(argument, assignmentIndex));
  301. String value = string_removeOuterWhiteSpace(string_after(argument, assignmentIndex));
  302. assignValue(settings, key, value, true);
  303. printText(U"Assigning ", key, U" to ", value, U" from input argument.\n");
  304. }
  305. }
  306. }