gravity.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. //
  2. // compiler_test.c
  3. // gravity
  4. //
  5. // Created by Marco Bambini on 24/03/16.
  6. // Copyright © 2016 CreoLabs. All rights reserved.
  7. //
  8. #include "gravity_compiler.h"
  9. #include "gravity_optionals.h"
  10. #include "gravity_utils.h"
  11. #include "gravity_core.h"
  12. #include "gravity_vm.h"
  13. #define DEFAULT_OUTPUT "gravity.g"
  14. typedef struct {
  15. bool processed;
  16. bool is_fuzzy;
  17. uint32_t ncount;
  18. uint32_t nsuccess;
  19. uint32_t nfailure;
  20. error_type_t expected_error;
  21. gravity_value_t expected_value;
  22. int32_t expected_row;
  23. int32_t expected_col;
  24. } unittest_data;
  25. typedef enum {
  26. OP_COMPILE, // just compile source code and exit
  27. OP_RUN, // just run an already compiled file
  28. OP_COMPILE_RUN, // compile source code and run it
  29. OP_INLINE_RUN, // compile and execute source passed inline
  30. OP_REPL, // run a read eval print loop
  31. OP_UNITTEST // unit test mode
  32. } op_type;
  33. static const char *input_file = NULL;
  34. static const char *output_file = DEFAULT_OUTPUT;
  35. static const char *unittest_folder = NULL;
  36. static const char *test_folder_path = NULL;
  37. static bool quiet_flag = false;
  38. static void report_error (gravity_vm *vm, error_type_t error_type, const char *message, error_desc_t error_desc, void *xdata) {
  39. (void) vm, (void) xdata;
  40. const char *type = "N/A";
  41. switch (error_type) {
  42. case GRAVITY_ERROR_NONE: type = "NONE"; break;
  43. case GRAVITY_ERROR_SYNTAX: type = "SYNTAX"; break;
  44. case GRAVITY_ERROR_SEMANTIC: type = "SEMANTIC"; break;
  45. case GRAVITY_ERROR_RUNTIME: type = "RUNTIME"; break;
  46. case GRAVITY_WARNING: type = "WARNING"; break;
  47. case GRAVITY_ERROR_IO: type = "I/O"; break;
  48. }
  49. if (error_type == GRAVITY_ERROR_RUNTIME) printf("RUNTIME ERROR: ");
  50. else printf("%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno);
  51. printf("%s\n", message);
  52. }
  53. static const char *load_file (const char *file, size_t *size, uint32_t *fileid, void *xdata, bool *is_static) {
  54. (void) fileid, (void) xdata;
  55. // this callback is called each time an import statement is parsed
  56. // file arg represents what user wrote after the import keyword, for example:
  57. // import "file2"
  58. // import "file2.gravity"
  59. // import "../file2"
  60. // import "/full_path_to_file2"
  61. // it is callback's responsibility to resolve file path based on current working directory
  62. // or based on user defined search paths
  63. // and returns:
  64. // size of file in *size
  65. // fileid (if any) in *fileid
  66. // content of file as return value of the function
  67. // fileid will then be used each time an error is reported by the compiler
  68. // so it is responsibility of this function to map somewhere the association
  69. // between fileid and real file/path name
  70. // fileid is not used in this example
  71. // xdata not used here but it the xdata field set in the delegate
  72. // please note than in this simple example the imported file must be
  73. // in the same folder as the main input file
  74. if (is_static) *is_static = false;
  75. if (!file_exists(file)) return NULL;
  76. return file_read(file, size);
  77. }
  78. // MARK: - Unit Test -
  79. static void unittest_init (const char *target_file, unittest_data *data) {
  80. (void) target_file;
  81. ++data->ncount;
  82. data->processed = false;
  83. }
  84. static void unittest_cleanup (const char *target_file, unittest_data *data) {
  85. (void) target_file, (void) data;
  86. }
  87. static void unittest_callback (gravity_vm *vm, error_type_t error_type, const char *description, const char *notes, gravity_value_t value, int32_t row, int32_t col, void *xdata) {
  88. (void) vm;
  89. unittest_data *data = (unittest_data *)xdata;
  90. data->expected_error = error_type;
  91. data->expected_value = value;
  92. data->expected_row = row;
  93. data->expected_col = col;
  94. if (notes) printf("\tNOTE: %s\n", notes);
  95. printf("\t%s\n", description);
  96. }
  97. static void unittest_error (gravity_vm *vm, error_type_t error_type, const char *message, error_desc_t error_desc, void *xdata) {
  98. (void) vm;
  99. unittest_data *data = (unittest_data *)xdata;
  100. if (data->processed == true) return; // ignore 2nd error
  101. data->processed = true;
  102. const char *type = "NONE";
  103. if (error_type == GRAVITY_ERROR_SYNTAX) type = "SYNTAX";
  104. else if (error_type == GRAVITY_ERROR_SEMANTIC) type = "SEMANTIC";
  105. else if (error_type == GRAVITY_ERROR_RUNTIME) type = "RUNTIME";
  106. else if (error_type == GRAVITY_WARNING) type = "WARNING";
  107. if (error_type == GRAVITY_ERROR_RUNTIME) printf("\tRUNTIME ERROR: ");
  108. else printf("\t%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno);
  109. printf("%s\n", message);
  110. bool same_error = (data->expected_error == error_type);
  111. bool same_row = (data->expected_row != -1) ? (data->expected_row == error_desc.lineno) : true;
  112. bool same_col = (data->expected_col != -1) ? (data->expected_col == error_desc.colno) : true;
  113. if (data->is_fuzzy) {
  114. ++data->nsuccess;
  115. printf("\tSUCCESS\n");
  116. return;
  117. }
  118. if (same_error && same_row && same_col) {
  119. ++data->nsuccess;
  120. printf("\tSUCCESS\n");
  121. } else {
  122. ++data->nfailure;
  123. printf("\tFAILURE\n");
  124. }
  125. }
  126. static const char *unittest_read (const char *path, size_t *size, uint32_t *fileid, void *xdata, bool *is_static) {
  127. (void) fileid, (void) xdata;
  128. if (is_static) *is_static = false;
  129. if (file_exists(path)) return file_read(path, size);
  130. // this unittest is able to resolve path only next to main test folder (not in nested folders)
  131. const char *newpath = file_buildpath(path, test_folder_path);
  132. if (!newpath) return NULL;
  133. const char *buffer = file_read(newpath, size);
  134. mem_free(newpath);
  135. return buffer;
  136. }
  137. static void unittest_scan (const char *folder_path, unittest_data *data) {
  138. DIRREF dir = directory_init(folder_path);
  139. if (!dir) return;
  140. #ifdef WIN32
  141. char outbuffer[MAX_PATH];
  142. #else
  143. char * outbuffer = NULL;
  144. #endif
  145. const char *target_file;
  146. while ((target_file = directory_read(dir, outbuffer))) {
  147. #ifdef WIN32
  148. char winbuffer[MAX_PATH];
  149. WideCharToMultiByte (CP_UTF8, 0, (LPCWSTR)target_file, -1, winbuffer, sizeof(winbuffer), NULL, NULL );
  150. target_file = (const char *)winbuffer;
  151. #endif
  152. // if file is a folder then start recursion
  153. const char *full_path = file_buildpath(target_file, folder_path);
  154. if (is_directory(full_path)) {
  155. // skip disabled folder
  156. if (strcmp(target_file, "disabled") == 0) continue;
  157. unittest_scan(full_path, data);
  158. continue;
  159. }
  160. // test only files with a .gravity extension
  161. if (strstr(full_path, ".gravity") == NULL) continue;
  162. data->is_fuzzy = (strstr(full_path, "/fuzzy/") != NULL);
  163. // load source code
  164. size_t size = 0;
  165. const char *source_code = file_read(full_path, &size);
  166. assert(source_code);
  167. // start unit test
  168. unittest_init(target_file, data);
  169. // compile and run source code
  170. printf("\n%d\tTest file: %s\n", data->ncount, target_file);
  171. printf("\tTest path: %s\n", full_path);
  172. mem_free(full_path);
  173. // initialize compiler and delegates
  174. gravity_delegate_t delegate = {
  175. .xdata = (void *)data,
  176. .error_callback = unittest_error,
  177. .unittest_callback = unittest_callback,
  178. .loadfile_callback = unittest_read
  179. };
  180. gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
  181. gravity_closure_t *closure = gravity_compiler_run(compiler, source_code, size, 0, false, false);
  182. gravity_vm *vm = gravity_vm_new(&delegate);
  183. gravity_compiler_transfer(compiler, vm);
  184. gravity_compiler_free(compiler);
  185. if (closure) {
  186. if (gravity_vm_runmain(vm, closure)) {
  187. data->processed = true;
  188. gravity_value_t result = gravity_vm_result(vm);
  189. if (data->is_fuzzy || gravity_value_equals(result, data->expected_value)) {
  190. ++data->nsuccess;
  191. printf("\tSUCCESS\n");
  192. } else {
  193. ++data->nfailure;
  194. printf("\tFAILURE\n");
  195. }
  196. gravity_value_free(NULL, data->expected_value);
  197. }
  198. }
  199. gravity_vm_free(vm);
  200. // case for empty files or simple declarations test
  201. if (!data->processed) {
  202. ++data->nsuccess;
  203. printf("\tSUCCESS\n");
  204. }
  205. // cleanup unitest
  206. unittest_cleanup(target_file, data);
  207. }
  208. }
  209. // MARK: - General -
  210. static void print_version (void) {
  211. printf("Gravity version %s (%s)\n", GRAVITY_VERSION, GRAVITY_BUILD_DATE);
  212. }
  213. static void print_help (void) {
  214. printf("usage: gravity [options]\n");
  215. printf("no options means enter interactive mode (not yet supported)\n");
  216. printf("Available options are:\n");
  217. printf("--version show version information and exit\n");
  218. printf("--help show command line usage and exit\n");
  219. printf("-c input_file compile input_file (default to gravity.json)\n");
  220. printf("-o output_file specify output file name\n");
  221. printf("-x input_file execute input_file (JSON format expected)\n");
  222. printf("-i source_code compile and execute source_code string\n");
  223. printf("-q don't print result and execution time\n");
  224. printf("-t Run unit tests (file_name is then treated as a folder)\n");
  225. printf("file_name compile file_name and executes it\n");
  226. }
  227. static op_type parse_args (int argc, const char* argv[]) {
  228. if (argc == 1) return OP_REPL;
  229. if (argc == 2 && strcmp(argv[1], "--version") == 0) {
  230. print_version();
  231. exit(0);
  232. }
  233. if (argc == 2 && strcmp(argv[1], "--help") == 0) {
  234. print_help();
  235. exit(0);
  236. }
  237. op_type type = OP_RUN;
  238. for (int i=1; i<argc; ++i) {
  239. if ((strcmp(argv[i], "-c") == 0) && (i+1 < argc)) {
  240. input_file = argv[++i];
  241. type = OP_COMPILE;
  242. }
  243. else if ((strcmp(argv[i], "-o") == 0) && (i+1 < argc)) {
  244. output_file = argv[++i];
  245. }
  246. else if ((strcmp(argv[i], "-x") == 0) && (i+1 < argc)) {
  247. input_file = argv[++i];
  248. type = OP_RUN;
  249. }
  250. else if ((strcmp(argv[i], "-i") == 0) && (i+1 < argc)) {
  251. input_file = argv[++i];
  252. type = OP_INLINE_RUN;
  253. }
  254. else if (strcmp(argv[i], "-q") == 0) {
  255. quiet_flag = true;
  256. }
  257. else if ((strcmp(argv[i], "-t") == 0) && (i+1 < argc)) {
  258. unittest_folder = argv[++i];
  259. type = OP_UNITTEST;
  260. }
  261. else {
  262. input_file = argv[i];
  263. type = OP_COMPILE_RUN;
  264. }
  265. }
  266. return type;
  267. }
  268. // MARK: - Special Modes -
  269. static void gravity_repl (void) {
  270. printf("REPL not yet implemented.\n");
  271. exit(0);
  272. /*
  273. // setup compiler/VM delegate
  274. gravity_delegate_t delegate = {
  275. .error_callback = report_error,
  276. };
  277. gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
  278. gravity_vm *vm = gravity_vm_new(&delegate);
  279. char *line = NULL;
  280. int length = 0;
  281. printf("Welcome to Gravity v%s\n", GRAVITY_VERSION);
  282. while((line = readline("> ", &length)) != NULL) {
  283. // to be implemented
  284. // gravity_compiler_eval(compiler, vm, line, length);
  285. free(line);
  286. }
  287. gravity_compiler_free(compiler);
  288. gravity_vm_free(vm);
  289. */
  290. }
  291. static void gravity_unittest (void) {
  292. unittest_data data = {
  293. .ncount = 0,
  294. .nsuccess = 0,
  295. .nfailure = 0
  296. };
  297. if (unittest_folder == NULL) {
  298. printf("Usage: gravity -t /path/to/unitest/\n");
  299. exit(-1);
  300. }
  301. // print console header
  302. printf("==============================================\n");
  303. printf("Gravity Unit Test Mode\n");
  304. printf("Gravity version %s\n", GRAVITY_VERSION);
  305. printf("Build date: %s\n", GRAVITY_BUILD_DATE);
  306. printf("==============================================\n");
  307. mem_init();
  308. nanotime_t tstart = nanotime();
  309. test_folder_path = unittest_folder;
  310. unittest_scan(unittest_folder, &data);
  311. nanotime_t tend = nanotime();
  312. double result = ((double)((data.nsuccess * 100)) / (double)data.ncount);
  313. printf("\n\n");
  314. printf("==============================================\n");
  315. printf("Total Tests: %d\n", data.ncount);
  316. printf("Total Successes: %d\n", data.nsuccess);
  317. printf("Total Failures: %d\n", data.nfailure);
  318. printf("Result: %.2f %%\n", result);
  319. printf("Time: %.4f ms\n", millitime(tstart, tend));
  320. printf("==============================================\n");
  321. printf("\n");
  322. // If we have 1 or more failures, return an error.
  323. if (data.nfailure != 0) {
  324. exit(1);
  325. }
  326. exit(0);
  327. }
  328. // MARK: -
  329. int main (int argc, const char* argv[]) {
  330. // parse arguments and return operation type
  331. op_type type = parse_args(argc, argv);
  332. // special repl case
  333. if (type == OP_REPL) gravity_repl();
  334. // special unit test mode
  335. if (type == OP_UNITTEST) gravity_unittest();
  336. // initialize memory debugger (if activated)
  337. mem_init();
  338. // closure to execute/serialize
  339. gravity_closure_t *closure = NULL;
  340. // optional compiler
  341. gravity_compiler_t *compiler = NULL;
  342. // setup compiler/VM delegate
  343. gravity_delegate_t delegate = {
  344. .error_callback = report_error,
  345. .loadfile_callback = load_file
  346. };
  347. // create VM
  348. gravity_vm *vm = gravity_vm_new(&delegate);
  349. // check if input file is source code that needs to be compiled
  350. if ((type == OP_COMPILE) || (type == OP_COMPILE_RUN) || (type == OP_INLINE_RUN)) {
  351. // load source code
  352. size_t size = 0;
  353. const char *source_code = NULL;
  354. if (type == OP_INLINE_RUN) {
  355. source_code = input_file;
  356. size = strlen(input_file);
  357. } else {
  358. source_code = file_read(input_file, &size);
  359. }
  360. // sanity check
  361. if (!source_code || !size) {
  362. printf("Error loading %s %s\n", (type == OP_INLINE_RUN) ? "source" : "file", input_file);
  363. goto cleanup;
  364. }
  365. // create closure to execute inline code
  366. if (type == OP_INLINE_RUN) {
  367. char *buffer = mem_alloc(NULL, size+1024);
  368. assert(buffer);
  369. size = snprintf(buffer, size+1024, "func main() {%s};", input_file);
  370. source_code = buffer;
  371. }
  372. // create compiler
  373. compiler = gravity_compiler_create(&delegate);
  374. // compile source code into a closure
  375. closure = gravity_compiler_run(compiler, source_code, size, 0, false, true);
  376. if (!closure) goto cleanup;
  377. // check if closure needs to be serialized
  378. if (type == OP_COMPILE) {
  379. bool result = gravity_compiler_serialize_infile(compiler, closure, output_file);
  380. if (!result) printf("Error serializing file %s\n", output_file);
  381. goto cleanup;
  382. }
  383. // op is OP_COMPILE_RUN so transfer memory from compiler to VM
  384. gravity_compiler_transfer(compiler, vm);
  385. } else if (type == OP_RUN) {
  386. // unserialize file
  387. closure = gravity_vm_loadfile(vm, input_file);
  388. if (!closure) {
  389. printf("Error while loading compile file %s\n", input_file);
  390. goto cleanup;
  391. }
  392. }
  393. // sanity check
  394. assert(closure);
  395. if (gravity_vm_runmain(vm, closure)) {
  396. gravity_value_t result = gravity_vm_result(vm);
  397. double t = gravity_vm_time(vm);
  398. char buffer[512];
  399. gravity_value_dump(vm, result, buffer, sizeof(buffer));
  400. if (!quiet_flag) {
  401. printf("RESULT: %s (in %.4f ms)\n\n", buffer, t);
  402. }
  403. }
  404. cleanup:
  405. if (compiler) gravity_compiler_free(compiler);
  406. if (vm) gravity_vm_free(vm);
  407. gravity_core_free();
  408. #if GRAVITY_MEMORY_DEBUG
  409. size_t current_memory = mem_leaks();
  410. if (current_memory != 0) {
  411. printf("--> VM leaks: %zu bytes\n", current_memory);
  412. mem_stat();
  413. } else {
  414. printf("\tNo VM leaks found!\n");
  415. }
  416. #endif
  417. return 0;
  418. }