| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- //
- // 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<argc; ++i) {
- if ((strcmp(argv[i], "-c") == 0) && (i+1 < argc)) {
- input_file = argv[++i];
- type = OP_COMPILE;
- }
- else if ((strcmp(argv[i], "-o") == 0) && (i+1 < argc)) {
- output_file = argv[++i];
- }
- else if ((strcmp(argv[i], "-x") == 0) && (i+1 < argc)) {
- input_file = argv[++i];
- type = OP_RUN;
- }
- else if ((strcmp(argv[i], "-i") == 0) && (i+1 < argc)) {
- input_file = argv[++i];
- type = OP_INLINE_RUN;
- }
- else if (strcmp(argv[i], "-q") == 0) {
- quiet_flag = true;
- }
- else if ((strcmp(argv[i], "-t") == 0) && (i+1 < argc)) {
- unittest_folder = argv[++i];
- type = OP_UNITTEST;
- }
- else {
- input_file = argv[i];
- type = OP_COMPILE_RUN;
- }
- }
- return type;
- }
- // MARK: - Special Modes -
- static void gravity_repl (void) {
- printf("REPL not yet implemented.\n");
- exit(0);
- /*
- // setup compiler/VM delegate
- gravity_delegate_t delegate = {
- .error_callback = report_error,
- };
- gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
- gravity_vm *vm = gravity_vm_new(&delegate);
- char *line = NULL;
- int length = 0;
- printf("Welcome to Gravity v%s\n", GRAVITY_VERSION);
- while((line = readline("> ", &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;
- }
|