Machine.cpp 18 KB

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