Machine.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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. ReadableString programPath = getFlag(settings, U"ProgramPath", output.language == ScriptLanguage::Batch ? U"program.exe" : U"program");
  246. programPath = file_getTheoreticalAbsolutePath(programPath, projectPath);
  247. // 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.
  248. if (getFlagAsInteger(settings, U"SkipIfBinaryExists") && file_getEntryType(programPath) == EntryType::File) {
  249. // SkipIfBinaryExists was active and the binary exists, so abort here to avoid redundant work.
  250. printText(U"Skipping build of ", projectFilePath, U" because the SkipIfBinaryExists flag was given.\n");
  251. return;
  252. }
  253. // Once we are done finding all source files, we can resolve the dependencies to create a graph connected by indices.
  254. resolveDependencies(context);
  255. if (getFlagAsInteger(settings, U"ListDependencies")) {
  256. printDependencies(context);
  257. }
  258. generateCompilationScript(output, context, settings, programPath);
  259. }
  260. // Using a folder path and input arguments for all projects.
  261. void buildProjects(ScriptTarget &output, const ReadableString &projectFolderPath, Machine &settings) {
  262. printText("Building all projects in ", projectFolderPath, "\n");
  263. file_getFolderContent(projectFolderPath, [&settings, &output](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
  264. if (entryType == EntryType::Folder) {
  265. buildProjects(output, entryPath, settings);
  266. } else if (entryType == EntryType::File) {
  267. ReadableString extension = string_upperCase(file_getExtension(entryName));
  268. if (string_match(extension, U"DSRPROJ")) {
  269. buildProject(output, entryPath, settings);
  270. }
  271. }
  272. });
  273. }
  274. void build(ScriptTarget &output, const ReadableString &projectPath, Machine &settings) {
  275. EntryType entryType = file_getEntryType(projectPath);
  276. printText("Building anything at ", projectPath, " which is ", entryType, "\n");
  277. if (entryType == EntryType::File) {
  278. String extension = string_upperCase(file_getExtension(projectPath));
  279. if (!string_match(extension, U"DSRPROJ")) {
  280. printText(U"Can't use the Build keyword with a file that is not a project!\n");
  281. } else {
  282. // Build the given project
  283. buildProject(output, projectPath, settings);
  284. }
  285. } else if (entryType == EntryType::Folder) {
  286. buildProjects(output, projectPath, settings);
  287. }
  288. }
  289. void argumentsToSettings(Machine &settings, const List<String> &arguments, int64_t firstArgument) {
  290. for (int a = firstArgument; a < arguments.length(); a++) {
  291. String argument = arguments[a];
  292. int64_t assignmentIndex = string_findFirst(argument, U'=');
  293. if (assignmentIndex == -1) {
  294. assignValue(settings, argument, U"1", true);
  295. printText(U"Assigning ", argument, U" to 1 from input argument.\n");
  296. } else {
  297. String key = string_removeOuterWhiteSpace(string_before(argument, assignmentIndex));
  298. String value = string_removeOuterWhiteSpace(string_after(argument, assignmentIndex));
  299. assignValue(settings, key, value, true);
  300. printText(U"Assigning ", key, U" to ", value, U" from input argument.\n");
  301. }
  302. }
  303. }