diff --git a/CMakeLists.txt b/CMakeLists.txt index 9993d45..0e46848 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,13 @@ cmake_minimum_required(VERSION 3.29) project(moka) +set(MOKA_LIB_DIR ${CMAKE_INSTALL_PREFIX}/lib/moka) set(CMAKE_EXPORT_COMPILE_COMMANDS On) add_subdirectory(lib) add_subdirectory(src) add_subdirectory(tests) +add_subdirectory(std) set_property(TARGET moka-core PROPERTY C_STANDARD 99) set_property(TARGET moka-tests PROPERTY C_STANDARD 99) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 05d5fa7..2be8495 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.29) project(moka-lib) +configure_file(conf.in.h ${CMAKE_SOURCE_DIR}/lib/conf.h) + add_library(moka-core commons.c status.c @@ -19,10 +21,12 @@ add_library(moka-core symtable.c native.c module.c + path.c + builtins.c ) target_compile_options(moka-core - PUBLIC -Wall -Wextra -g + PUBLIC -Wall -Wextra -g -fPIC ) target_include_directories(moka-core diff --git a/lib/builtins.c b/lib/builtins.c new file mode 100644 index 0000000..2476399 --- /dev/null +++ b/lib/builtins.c @@ -0,0 +1,48 @@ +#include "builtins.h" +#include "node.h" +#include "path.h" +#include "module.h" + +void register_builtins(struct moka* moka) +{ + assert(moka); + moka_decl_native(moka, "println", mk_println); + moka_decl_native(moka, "define", mk_define); +} + +MOKA mk_println(struct moka* moka, struct vec* args) +{ + assert(moka); + assert(args); + + int line = 0; + + for (size_t i=0; isize; i++) + { + MOKA val = (MOKA) args->data[i]; + if (i > 0) { printf(" "); } + else { line = moka_get_lazy(moka, val)->line; } + moka_dump(moka, MK_EVAL(val)); + } + + printf("\n"); + + return moka_push_int(moka, 0, line); +} + +MOKA mk_define(struct moka* moka, struct vec* args) +{ + assert(moka); assert(args); + assert(args->size == 2); + + MOKA target = (MOKA) args->data[0]; + MOKA value = MK_EVAL((MOKA) args->data[1]); + + struct node* node = moka_get_lazy(moka, target); + + moka_push(moka, value); + assert(node->token); + moka_decl_var(moka, node->token->value, value); + + return value; +} diff --git a/lib/builtins.h b/lib/builtins.h new file mode 100644 index 0000000..c0cb800 --- /dev/null +++ b/lib/builtins.h @@ -0,0 +1,14 @@ +#ifndef MK_BUILTINS_H +#define MK_BUILTINS_H + +#include "commons.h" +#include "moka.h" + +#define MK_EVAL(X) moka_eval_lazy(moka, X) + +void register_builtins(struct moka* moka); + +MOKA mk_println(struct moka* moka, struct vec* args); +MOKA mk_define(struct moka* moka, struct vec* args); + +#endif diff --git a/lib/compiler.c b/lib/compiler.c index 9c10f2a..a9f8f56 100644 --- a/lib/compiler.c +++ b/lib/compiler.c @@ -1,4 +1,8 @@ #include "compiler.h" +#include "moka.h" +#include "module.h" +#include "path.h" +#include "conf.h" void compiler_init(struct compiler* self, struct status* status) @@ -15,7 +19,7 @@ void compiler_free(struct compiler* self) void compiler_compile(struct compiler* self, struct node* node, struct prog* prog, - struct symtable* symtable) + struct moka* moka) { assert(self); assert(node); assert(prog); @@ -25,26 +29,30 @@ void compiler_compile(struct compiler* self, for (size_t i=0; ichildren.size; i++) { struct node* child = node->children.data[i]; - compiler_compile(self, child, prog, symtable); + compiler_compile(self, child, prog, moka); } } break; case NODE_CALL: { + struct node* child = node->children.data[0]; + + if (strcmp(child->token->value, "import") == 0) + { + compiler_import(self, moka, node); + break; + } + for (size_t i=0; ichildren.size - 1; i++) { - // size_t k = node->children.size - 1 - i; - // struct node* child = node->children.data[k]; - // compiler_compile(self, child, prog, symtable); size_t k = node->children.size - 1 - i; - struct node* child = node->children.data[k]; + struct node* mychild = node->children.data[k]; struct value* val = malloc(sizeof(struct value)); - value_init_lazy(val, child, child->line); + value_init_lazy(val, mychild, mychild->line); size_t addr = prog_add_new_value(prog, val); prog_add_instruction(prog, OP_PUSH, addr); } - struct node* child = node->children.data[0]; - compiler_compile(self, child, prog, symtable); + compiler_compile(self, child, prog, moka); prog_add_instruction(prog, OP_CALL, node->children.size - 1); } break; @@ -88,8 +96,10 @@ void compiler_compile(struct compiler* self, } break; case NODE_IDENT: { - struct entry const* entry = symtable_try_get(symtable, - node->token->value); + struct entry const* entry = symtable_try_get( + &moka->symtable, + node->token->value + ); if (!entry) { status_push(self->status, STATUS_ERROR, node->line, @@ -114,3 +124,102 @@ void compiler_compile(struct compiler* self, } break; } } + + +void compiler_import(struct compiler* self, + struct moka* moka, + struct node* node) +{ + (void) self; + + struct node* arg = node->children.data[1]; + char const* name = arg->token->value; + + if (!path_is_local(name)) + { + char source_name_loc[MK_STRLEN]; + path_apply_ext(name, "mk", source_name_loc, MK_STRLEN); + char source_name[MK_STRLEN*2]; + snprintf(source_name, MK_STRLEN * 2, "%s/%s", + MOKA_LIB_DIR, + source_name_loc); + + char lib_name_tmp[MK_STRLEN]; + path_apply_ext(name, "so", lib_name_tmp, MK_STRLEN); + char lib_name_loc[MK_STRLEN]; + path_apply_prefix(lib_name_tmp, "lib", lib_name_loc, MK_STRLEN); + char lib_name[MK_STRLEN * 2]; + snprintf(lib_name, MK_STRLEN * 2, "%s/%s", + MOKA_LIB_DIR, + lib_name_loc); + + if (path_exists(source_name)) + { + struct module* module = malloc(sizeof(struct module)); + module_init(module); + module_load_from_file(module, source_name); + + char modname[MK_STRLEN]; + path_get_mod_name(name, modname, MK_STRLEN); + + moka_import_module(moka, modname, module); + return; + } + else if (path_exists(lib_name)) + { + struct module* module = malloc(sizeof(struct module)); + module_init(module); + module_load_from_dl(module, lib_name); + + char modname[MK_STRLEN]; + path_get_mod_name(name, modname, MK_STRLEN); + + moka_import_module(moka, modname, module); + return; + } + } + else + { + char source_name[MK_STRLEN]; + path_apply_ext(name, "mk", source_name, MK_STRLEN); + + + char lib_name_tmp[MK_STRLEN]; + path_apply_ext(name, "so", lib_name_tmp, MK_STRLEN); + char lib_name[MK_STRLEN]; + path_apply_prefix(lib_name_tmp, "lib", lib_name, MK_STRLEN); + + if (path_exists(source_name)) + { + struct module* module = malloc(sizeof(struct module)); + module_init(module); + module_load_from_file(module, source_name); + + char modname[MK_STRLEN]; + path_get_mod_name(name, modname, MK_STRLEN); + + moka_import_module(moka, modname, module); + return; + } + else if (path_exists(lib_name)) + { + struct module* module = malloc(sizeof(struct module)); + module_init(module); + module_load_from_dl(module, lib_name); + + char modname[MK_STRLEN]; + path_get_mod_name(name, modname, MK_STRLEN); + + moka_import_module(moka, modname, module); + return; + } + } + + status_push( + self->status, + STATUS_ERROR, + node->line, + "unknown module <%s>", + name + ); +} diff --git a/lib/compiler.h b/lib/compiler.h index c2b255f..21b1d08 100644 --- a/lib/compiler.h +++ b/lib/compiler.h @@ -19,5 +19,10 @@ void compiler_free(struct compiler* self); void compiler_compile(struct compiler* self, struct node* node, struct prog* prog, - struct symtable* symtable); + struct moka* moka); + +void compiler_import(struct compiler* self, + struct moka* moka, + struct node* node); + #endif diff --git a/lib/conf.h b/lib/conf.h new file mode 100644 index 0000000..651d2dc --- /dev/null +++ b/lib/conf.h @@ -0,0 +1,7 @@ +#ifndef MK_CONF_H +#define MK_CONF_H + +#define MOKA_LIB_DIR "/usr/local/lib/moka" + + +#endif diff --git a/lib/conf.in.h b/lib/conf.in.h new file mode 100644 index 0000000..1777a53 --- /dev/null +++ b/lib/conf.in.h @@ -0,0 +1,7 @@ +#ifndef MK_CONF_H +#define MK_CONF_H + +#cmakedefine MOKA_LIB_DIR "@MOKA_LIB_DIR@" + + +#endif diff --git a/lib/exec.c b/lib/exec.c index e7f9b9f..cc42ae3 100644 --- a/lib/exec.c +++ b/lib/exec.c @@ -47,6 +47,11 @@ void exec_instr(struct exec* self, self->pc++; } break; + case OP_LOCAL_LOAD: { + moka_push(moka, param); + self->pc++; + } break; + case OP_PUSH: { struct value* value = prog->values.data[param]; switch (value->type) diff --git a/lib/lexer.c b/lib/lexer.c index 5272853..ccdfa6d 100644 --- a/lib/lexer.c +++ b/lib/lexer.c @@ -401,7 +401,8 @@ bool lexer_is_ident(struct lexer* self, size_t index) || c == '_' || c == '!' || c == '?' - || c == '-'; + || c == '-' + || c == ':'; } struct token* lexer_try_new_text(struct lexer* self, diff --git a/lib/module.c b/lib/module.c index e391878..6b18358 100644 --- a/lib/module.c +++ b/lib/module.c @@ -1,11 +1,16 @@ +#include #include "module.h" +#include "builtins.h" void module_init(struct module* self) { assert(self); status_init(&self->status); moka_init(&self->moka, &self->status); + register_builtins(&self->moka); + prog_init(&self->prog); + self->handle = NULL; } void module_free(struct module* self) @@ -14,6 +19,36 @@ void module_free(struct module* self) status_free(&self->status); prog_free(&self->prog); moka_free(&self->moka); + if (self->handle) + { + dlclose(self->handle); + self->handle = NULL; + } +} + +int module_load_from_dl(struct module* self, char const* path) +{ + assert(self); + assert(path); + + self->handle = dlopen(path, RTLD_NOW); + + if (!self->handle) + { + return EXIT_FAILURE; + } + + void (*f)(struct module*) = dlsym(self->handle, "create_module"); + + if (!f) + { + dlclose(self->handle); + return EXIT_FAILURE; + } + + f(self); + + return EXIT_SUCCESS; } int module_load_from_file(struct module* self, char const* path) @@ -62,7 +97,7 @@ int module_load_from_str(struct module* self, char const* source) struct compiler compiler; compiler_init(&compiler, &self->status); - compiler_compile(&compiler, root, &self->prog, &self->moka.symtable); + compiler_compile(&compiler, root, &self->prog, &self->moka); if (!status_is_ok(&self->status)) { diff --git a/lib/module.h b/lib/module.h index d22fa7b..7cd7cf7 100644 --- a/lib/module.h +++ b/lib/module.h @@ -16,11 +16,13 @@ struct module struct status status; struct prog prog; struct moka moka; + void* handle; }; void module_init(struct module* self); void module_free(struct module* self); +int module_load_from_dl(struct module* self, char const* path); int module_load_from_file(struct module* self, char const* path); int module_load_from_str(struct module* self, char const* source); diff --git a/lib/moka.c b/lib/moka.c index 7c35bea..d5de2fb 100644 --- a/lib/moka.c +++ b/lib/moka.c @@ -6,7 +6,7 @@ #include "module.h" void moka_mod_init(struct moka_mod* self, - char* name, + char const* name, struct module* new_module) { assert(self); @@ -63,6 +63,122 @@ void moka_free(struct moka* self) vec_free(&self->global_values); } +void moka_import_module(struct moka* self, + char const* name, + struct module* module) +{ + assert(self); + assert(name); + assert(module); + + struct symtable* mod_sym = &module->moka.symtable; + struct env* env = mod_sym->root; + + struct frame* mod_frame = moka_frame(&module->moka); + struct vec* mod_locals = &mod_frame->local_values; + struct vec* mod_globals = &module->moka.global_values; + + for (size_t i=0; ientries.size; i++) + { + struct entry const* entry = env->entries.data[i]; + struct value* val; + if (entry->is_local) + { + val = mod_locals->data[entry->addr]; + } + else + { + val = mod_globals->data[entry->addr]; + } + + char new_name[MK_STRLEN]; + snprintf(new_name, MK_STRLEN, "%s::%s", name, entry->name); + + switch (val->type) + { + case TY_NATIVE: { + moka_decl_native( + self, + new_name, + val->data.native->fun + ); + } break; + + case TY_SYMBOL: { + moka_decl_var( + self, + new_name, + moka_push_symbol(self, + val->data.sym, + val->line)); + } break; + + case TY_STRING: { + moka_decl_var( + self, + new_name, + moka_push_string(self, + val->data.str, + val->line)); + } break; + + case TY_INT: { + moka_decl_var( + self, + new_name, + moka_push_int(self, + val->data.integer, + val->line)); + } break; + + default: { + fprintf(stderr, + "cannot import value of type <%s>\n", + TypeKindStr[val->type]); + abort(); + } break; + } + + } + + struct moka_mod* mod = malloc(sizeof(struct moka_mod)); + moka_mod_init(mod, (char*) name, module); + + vec_push(&self->modules, mod); +} + +struct module* moka_try_get_module(struct moka* self, + char const* name) +{ + assert(self); + assert(name); + + for (size_t i=0; imodules.size; i++) + { + struct moka_mod* mod = self->modules.data[i]; + + if (strcmp(name, mod->name)) + { + return mod->module; + } + } + + return NULL; +} + +void moka_decl_var(struct moka* self, + char* name, + MOKA value) +{ + assert(self); assert(name); + + symtable_declare( + &self->symtable, + name, + value, + true); +} + void moka_decl_native(struct moka* self, char* name, native_fun_t fun) @@ -195,6 +311,14 @@ MOKA moka_call(struct moka* self, int arg_count) return moka_top(self); } +MOKA moka_push(struct moka* self, MOKA value) +{ + assert(self); + struct frame* frame = moka_frame(self); + vec_push(&frame->stack, (void*) value); + return value; +} + MOKA moka_push_int(struct moka* self, int value, int line) { assert(self); @@ -317,6 +441,7 @@ size_t moka_get_ref(struct moka* self, MOKA value) assert(self); struct frame* frame = moka_frame(self); struct value* val = frame->local_values.data[value]; + assert(val->type == TY_REF); return val->data.ref; } @@ -369,7 +494,7 @@ MOKA moka_eval_lazy(struct moka* self, MOKA lazy_value) &compiler, node, &prog, - &self->symtable); + self); if (!status_is_ok(self->status)) { diff --git a/lib/moka.h b/lib/moka.h index e1552a6..0285d10 100644 --- a/lib/moka.h +++ b/lib/moka.h @@ -27,8 +27,8 @@ struct moka struct vec modules; }; -void moka_mod_init(struct moka_mod* self, - char* name, +void moka_mod_init(struct moka_mod* self, + char const* name, struct module* new_module); void moka_mod_free(struct moka_mod* self); @@ -37,6 +37,18 @@ void frame_init(struct frame* self); void frame_free(struct frame* self); void moka_free(struct moka* self); + +void moka_import_module(struct moka* self, + char const* name, + struct module* module); + +struct module* moka_try_get_module(struct moka* self, + char const* name); + +void moka_decl_var(struct moka* self, + char* name, + MOKA value); + void moka_decl_native(struct moka* self, char* name, native_fun_t fun); @@ -52,6 +64,9 @@ TypeKind moka_type_of(struct moka* self, MOKA value); void moka_dump(struct moka* self, MOKA value); MOKA moka_call(struct moka* self, int arg_count); + +MOKA moka_push(struct moka* self, MOKA value); + MOKA moka_push_int(struct moka* self, int value, int line); int moka_get_int(struct moka* self, MOKA value); diff --git a/lib/path.c b/lib/path.c new file mode 100644 index 0000000..e91b2f9 --- /dev/null +++ b/lib/path.c @@ -0,0 +1,162 @@ +#include "path.h" + +bool path_is_local(char const* path) +{ + assert(path); + + if (strlen(path) == 0) + { + return false; + } + + return path[0] == '.'; +} + +void path_get_mod_name(char const* path, char* buffer, size_t size) +{ + assert(path); + size_t sz = strlen(path); + size_t k = 0; + + for (size_t i=0; i= size) { break; } + buffer[k] = path[i]; + k++; + } + } + + buffer[k] = '\0'; +} + +bool path_exists(char const* path) +{ + FILE* file; + + if ( (file = fopen(path, "r")) ) + { + fclose(file); + return true; + } + + return false; +} + +size_t path_apply_ext(char const* path, + char const* ext, + char* buffer, + size_t size) +{ + assert(path); assert(ext); assert(buffer); + + ssize_t ext_idx = -1; + size_t path_sz = strlen(path); + + char result[MK_STRLEN]; + memset(result, '\0', sizeof(char) * MK_STRLEN); + memcpy(result, path, sizeof(char) * path_sz); + + for (size_t i=0; istack_addr++; } break; diff --git a/lib/value.h b/lib/value.h index 00f89d5..8e7f832 100644 --- a/lib/value.h +++ b/lib/value.h @@ -38,6 +38,7 @@ void value_init_symbol(struct value* self, char const* value, int line); void value_init_ref(struct value* self, size_t value, int line); void value_init_native(struct value* self, struct native* value, int line); void value_init_lazy(struct value* self, struct node* value, int line); + void value_free(struct value* self); #endif diff --git a/src/main.c b/src/main.c index 34ce03d..68e37e4 100644 --- a/src/main.c +++ b/src/main.c @@ -16,13 +16,6 @@ int main(int argc, char** argv) int ret = module_load_from_file(&module, argv[1]); - if (moka_has_top(&module.moka)) - { - MOKA value = moka_top(&module.moka); - moka_dump(&module.moka, value); - printf("\n"); - } - module_free(&module); return ret; diff --git a/std/CMakeLists.txt b/std/CMakeLists.txt new file mode 100644 index 0000000..ba92d85 --- /dev/null +++ b/std/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.29) + +project(moka-std) + +function(add_stdlib target source) + add_library(${target} SHARED + ${source} + ) + + add_dependencies(${target} moka-core) + + target_link_libraries(${target} + PUBLIC moka-core + ) + + install(TARGETS ${target} LIBRARY DESTINATION lib/moka) +endfunction() + diff --git a/tests/main.c b/tests/main.c index 2fd6297..ba4b0e8 100644 --- a/tests/main.c +++ b/tests/main.c @@ -2,6 +2,7 @@ #include #include "lexer.h" #include "parser.h" +#include "path.h" int main() { @@ -9,6 +10,7 @@ int main() register_lexer(s); register_parser(s); + register_path(s); SRunner* runner = srunner_create(s); srunner_run_all(runner, CK_VERBOSE); diff --git a/tests/path.h b/tests/path.h new file mode 100644 index 0000000..88f48a6 --- /dev/null +++ b/tests/path.h @@ -0,0 +1,82 @@ +#ifndef MK_TEST_PATH_H +#define MK_TEST_PATH_H + +#include +#include + +START_TEST(path_check_local) +{ + ck_assert(true == path_is_local("./hello/world")); + ck_assert(true == path_is_local("../hello/world")); + ck_assert(false == path_is_local("")); + ck_assert(false == path_is_local("hello/world")); + ck_assert(false == path_is_local("/hello/world")); + ck_assert(false == path_is_local("/../hello/world")); +} +END_TEST + +START_TEST(test_path_get_mod_name) +{ + size_t const SZ = MK_STRLEN; + char buffer[SZ]; + + path_get_mod_name("./hello/world", buffer, SZ); + ck_assert_str_eq("world", buffer); + + path_get_mod_name("./hello/world/", buffer, SZ); + ck_assert_str_eq("world", buffer); + + path_get_mod_name("./hello.ext", buffer, SZ); + ck_assert_str_eq("hello", buffer); + + path_get_mod_name("./hello.ext/world.com", buffer, SZ); + ck_assert_str_eq("world", buffer); + + path_get_mod_name("./hello.ext/world.com/", buffer, SZ); + ck_assert_str_eq("world", buffer); +} +END_TEST + +START_TEST(test_path_apply_ext) +{ + size_t const SZ = MK_STRLEN; + char buffer[SZ]; + + path_apply_ext("./hello/world", "abc", buffer, SZ); + ck_assert_str_eq("./hello/world.abc", buffer); + + path_apply_ext("world.mk", "abc", buffer, SZ); + ck_assert_str_eq("world.abc", buffer); + + path_apply_ext("./hello/world.xy", "xy", buffer, SZ); + ck_assert_str_eq("./hello/world.xy", buffer); +} +END_TEST + +START_TEST(test_path_apply_prefix) +{ + size_t const SZ = MK_STRLEN; + char buffer[SZ]; + + path_apply_prefix("./hello/world.so", "lib", buffer, SZ); + ck_assert_str_eq("./hello/libworld.so", buffer); + + path_apply_prefix("./hello/libworld.so", "lib", buffer, SZ); + ck_assert_str_eq("./hello/libworld.so", buffer); + + path_apply_prefix("./hello/alibworld.so", "lib", buffer, SZ); + ck_assert_str_eq("./hello/libalibworld.so", buffer); +} +END_TEST + +void register_path(Suite* suite) +{ + TCase* tcase = tcase_create("path"); + suite_add_tcase(suite, tcase); + tcase_add_test(tcase, path_check_local); + tcase_add_test(tcase, test_path_get_mod_name); + tcase_add_test(tcase, test_path_apply_ext); + tcase_add_test(tcase, test_path_apply_prefix); +} + +#endif