// // compiler_test.c // gravity // // Created by Marco Bambini on 24/03/16. // Copyright © 2016 CreoLabs. All rights reserved. // #include "gravity_compiler.h" #include "gravity_optionals.h" #include "gravity_utils.h" #include "gravity_core.h" #include "gravity_vm.h" #define DEFAULT_OUTPUT "gravity.g" typedef struct { bool processed; bool is_fuzzy; uint32_t ncount; uint32_t nsuccess; uint32_t nfailure; error_type_t expected_error; gravity_value_t expected_value; int32_t expected_row; int32_t expected_col; } unittest_data; typedef enum { OP_COMPILE, // just compile source code and exit OP_RUN, // just run an already compiled file OP_COMPILE_RUN, // compile source code and run it OP_INLINE_RUN, // compile and execute source passed inline OP_REPL, // run a read eval print loop OP_UNITTEST // unit test mode } op_type; static const char *input_file = NULL; static const char *output_file = DEFAULT_OUTPUT; static const char *unittest_folder = NULL; static const char *test_folder_path = NULL; static bool quiet_flag = false; static void report_error (gravity_vm *vm, error_type_t error_type, const char *message, error_desc_t error_desc, void *xdata) { (void) vm, (void) xdata; const char *type = "N/A"; switch (error_type) { case GRAVITY_ERROR_NONE: type = "NONE"; break; case GRAVITY_ERROR_SYNTAX: type = "SYNTAX"; break; case GRAVITY_ERROR_SEMANTIC: type = "SEMANTIC"; break; case GRAVITY_ERROR_RUNTIME: type = "RUNTIME"; break; case GRAVITY_WARNING: type = "WARNING"; break; case GRAVITY_ERROR_IO: type = "I/O"; break; } if (error_type == GRAVITY_ERROR_RUNTIME) printf("RUNTIME ERROR: "); else printf("%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno); printf("%s\n", message); } static const char *load_file (const char *file, size_t *size, uint32_t *fileid, void *xdata, bool *is_static) { (void) fileid, (void) xdata; // this callback is called each time an import statement is parsed // file arg represents what user wrote after the import keyword, for example: // import "file2" // import "file2.gravity" // import "../file2" // import "/full_path_to_file2" // it is callback's responsibility to resolve file path based on current working directory // or based on user defined search paths // and returns: // size of file in *size // fileid (if any) in *fileid // content of file as return value of the function // fileid will then be used each time an error is reported by the compiler // so it is responsibility of this function to map somewhere the association // between fileid and real file/path name // fileid is not used in this example // xdata not used here but it the xdata field set in the delegate // please note than in this simple example the imported file must be // in the same folder as the main input file if (is_static) *is_static = false; if (!file_exists(file)) return NULL; return file_read(file, size); } // MARK: - Unit Test - static void unittest_init (const char *target_file, unittest_data *data) { (void) target_file; ++data->ncount; data->processed = false; } static void unittest_cleanup (const char *target_file, unittest_data *data) { (void) target_file, (void) data; } 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) { (void) vm; unittest_data *data = (unittest_data *)xdata; data->expected_error = error_type; data->expected_value = value; data->expected_row = row; data->expected_col = col; if (notes) printf("\tNOTE: %s\n", notes); printf("\t%s\n", description); } static void unittest_error (gravity_vm *vm, error_type_t error_type, const char *message, error_desc_t error_desc, void *xdata) { (void) vm; unittest_data *data = (unittest_data *)xdata; if (data->processed == true) return; // ignore 2nd error data->processed = true; const char *type = "NONE"; if (error_type == GRAVITY_ERROR_SYNTAX) type = "SYNTAX"; else if (error_type == GRAVITY_ERROR_SEMANTIC) type = "SEMANTIC"; else if (error_type == GRAVITY_ERROR_RUNTIME) type = "RUNTIME"; else if (error_type == GRAVITY_WARNING) type = "WARNING"; if (error_type == GRAVITY_ERROR_RUNTIME) printf("\tRUNTIME ERROR: "); else printf("\t%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno); printf("%s\n", message); bool same_error = (data->expected_error == error_type); bool same_row = (data->expected_row != -1) ? (data->expected_row == error_desc.lineno) : true; bool same_col = (data->expected_col != -1) ? (data->expected_col == error_desc.colno) : true; if (data->is_fuzzy) { ++data->nsuccess; printf("\tSUCCESS\n"); return; } if (same_error && same_row && same_col) { ++data->nsuccess; printf("\tSUCCESS\n"); } else { ++data->nfailure; printf("\tFAILURE\n"); } } static const char *unittest_read (const char *path, size_t *size, uint32_t *fileid, void *xdata, bool *is_static) { (void) fileid, (void) xdata; if (is_static) *is_static = false; if (file_exists(path)) return file_read(path, size); // this unittest is able to resolve path only next to main test folder (not in nested folders) const char *newpath = file_buildpath(path, test_folder_path); if (!newpath) return NULL; const char *buffer = file_read(newpath, size); mem_free(newpath); return buffer; } static void unittest_scan (const char *folder_path, unittest_data *data) { DIRREF dir = directory_init(folder_path); if (!dir) return; #ifdef WIN32 char outbuffer[MAX_PATH]; #else char * outbuffer = NULL; #endif const char *target_file; while ((target_file = directory_read(dir, outbuffer))) { #ifdef WIN32 char winbuffer[MAX_PATH]; WideCharToMultiByte (CP_UTF8, 0, (LPCWSTR)target_file, -1, winbuffer, sizeof(winbuffer), NULL, NULL ); target_file = (const char *)winbuffer; #endif // if file is a folder then start recursion const char *full_path = file_buildpath(target_file, folder_path); if (is_directory(full_path)) { // skip disabled folder if (strcmp(target_file, "disabled") == 0) continue; unittest_scan(full_path, data); continue; } // test only files with a .gravity extension if (strstr(full_path, ".gravity") == NULL) continue; data->is_fuzzy = (strstr(full_path, "/fuzzy/") != NULL); // load source code size_t size = 0; const char *source_code = file_read(full_path, &size); assert(source_code); // start unit test unittest_init(target_file, data); // compile and run source code printf("\n%d\tTest file: %s\n", data->ncount, target_file); printf("\tTest path: %s\n", full_path); mem_free(full_path); // initialize compiler and delegates gravity_delegate_t delegate = { .xdata = (void *)data, .error_callback = unittest_error, .unittest_callback = unittest_callback, .loadfile_callback = unittest_read }; gravity_compiler_t *compiler = gravity_compiler_create(&delegate); gravity_closure_t *closure = gravity_compiler_run(compiler, source_code, size, 0, false, false); gravity_vm *vm = gravity_vm_new(&delegate); gravity_compiler_transfer(compiler, vm); gravity_compiler_free(compiler); if (closure) { if (gravity_vm_runmain(vm, closure)) { data->processed = true; gravity_value_t result = gravity_vm_result(vm); if (data->is_fuzzy || gravity_value_equals(result, data->expected_value)) { ++data->nsuccess; printf("\tSUCCESS\n"); } else { ++data->nfailure; printf("\tFAILURE\n"); } gravity_value_free(NULL, data->expected_value); } } gravity_vm_free(vm); // case for empty files or simple declarations test if (!data->processed) { ++data->nsuccess; printf("\tSUCCESS\n"); } // cleanup unitest unittest_cleanup(target_file, data); } } // MARK: - General - static void print_version (void) { printf("Gravity version %s (%s)\n", GRAVITY_VERSION, GRAVITY_BUILD_DATE); } static void print_help (void) { printf("usage: gravity [options]\n"); printf("no options means enter interactive mode (not yet supported)\n"); printf("Available options are:\n"); printf("--version show version information and exit\n"); printf("--help show command line usage and exit\n"); printf("-c input_file compile input_file (default to gravity.json)\n"); printf("-o output_file specify output file name\n"); printf("-x input_file execute input_file (JSON format expected)\n"); printf("-i source_code compile and execute source_code string\n"); printf("-q don't print result and execution time\n"); printf("-t Run unit tests (file_name is then treated as a folder)\n"); printf("file_name compile file_name and executes it\n"); } static op_type parse_args (int argc, const char* argv[]) { if (argc == 1) return OP_REPL; if (argc == 2 && strcmp(argv[1], "--version") == 0) { print_version(); exit(0); } if (argc == 2 && strcmp(argv[1], "--help") == 0) { print_help(); exit(0); } op_type type = OP_RUN; for (int i=1; i ", &length)) != NULL) { // to be implemented // gravity_compiler_eval(compiler, vm, line, length); free(line); } gravity_compiler_free(compiler); gravity_vm_free(vm); */ } static void gravity_unittest (void) { unittest_data data = { .ncount = 0, .nsuccess = 0, .nfailure = 0 }; if (unittest_folder == NULL) { printf("Usage: gravity -t /path/to/unitest/\n"); exit(-1); } // print console header printf("==============================================\n"); printf("Gravity Unit Test Mode\n"); printf("Gravity version %s\n", GRAVITY_VERSION); printf("Build date: %s\n", GRAVITY_BUILD_DATE); printf("==============================================\n"); mem_init(); nanotime_t tstart = nanotime(); test_folder_path = unittest_folder; unittest_scan(unittest_folder, &data); nanotime_t tend = nanotime(); double result = ((double)((data.nsuccess * 100)) / (double)data.ncount); printf("\n\n"); printf("==============================================\n"); printf("Total Tests: %d\n", data.ncount); printf("Total Successes: %d\n", data.nsuccess); printf("Total Failures: %d\n", data.nfailure); printf("Result: %.2f %%\n", result); printf("Time: %.4f ms\n", millitime(tstart, tend)); printf("==============================================\n"); printf("\n"); // If we have 1 or more failures, return an error. if (data.nfailure != 0) { exit(1); } exit(0); } // MARK: - int main (int argc, const char* argv[]) { // parse arguments and return operation type op_type type = parse_args(argc, argv); // special repl case if (type == OP_REPL) gravity_repl(); // special unit test mode if (type == OP_UNITTEST) gravity_unittest(); // initialize memory debugger (if activated) mem_init(); // closure to execute/serialize gravity_closure_t *closure = NULL; // optional compiler gravity_compiler_t *compiler = NULL; // setup compiler/VM delegate gravity_delegate_t delegate = { .error_callback = report_error, .loadfile_callback = load_file }; // create VM gravity_vm *vm = gravity_vm_new(&delegate); // check if input file is source code that needs to be compiled if ((type == OP_COMPILE) || (type == OP_COMPILE_RUN) || (type == OP_INLINE_RUN)) { // load source code size_t size = 0; const char *source_code = NULL; if (type == OP_INLINE_RUN) { source_code = input_file; size = strlen(input_file); } else { source_code = file_read(input_file, &size); } // sanity check if (!source_code || !size) { printf("Error loading %s %s\n", (type == OP_INLINE_RUN) ? "source" : "file", input_file); goto cleanup; } // create closure to execute inline code if (type == OP_INLINE_RUN) { char *buffer = mem_alloc(NULL, size+1024); assert(buffer); size = snprintf(buffer, size+1024, "func main() {%s};", input_file); source_code = buffer; } // create compiler compiler = gravity_compiler_create(&delegate); // compile source code into a closure closure = gravity_compiler_run(compiler, source_code, size, 0, false, true); if (!closure) goto cleanup; // check if closure needs to be serialized if (type == OP_COMPILE) { bool result = gravity_compiler_serialize_infile(compiler, closure, output_file); if (!result) printf("Error serializing file %s\n", output_file); goto cleanup; } // op is OP_COMPILE_RUN so transfer memory from compiler to VM gravity_compiler_transfer(compiler, vm); } else if (type == OP_RUN) { // unserialize file closure = gravity_vm_loadfile(vm, input_file); if (!closure) { printf("Error while loading compile file %s\n", input_file); goto cleanup; } } // sanity check assert(closure); if (gravity_vm_runmain(vm, closure)) { gravity_value_t result = gravity_vm_result(vm); double t = gravity_vm_time(vm); char buffer[512]; gravity_value_dump(vm, result, buffer, sizeof(buffer)); if (!quiet_flag) { printf("RESULT: %s (in %.4f ms)\n\n", buffer, t); } } cleanup: if (compiler) gravity_compiler_free(compiler); if (vm) gravity_vm_free(vm); gravity_core_free(); #if GRAVITY_MEMORY_DEBUG size_t current_memory = mem_leaks(); if (current_memory != 0) { printf("--> VM leaks: %zu bytes\n", current_memory); mem_stat(); } else { printf("\tNo VM leaks found!\n"); } #endif return 0; }