diff options
| -rw-r--r-- | Makefile | 23 | ||||
| -rw-r--r-- | docs/PLUGINS.md | 170 | ||||
| -rw-r--r-- | plugins/befunge.c | 5 | ||||
| -rw-r--r-- | plugins/brainfuck.c | 5 | ||||
| -rw-r--r-- | plugins/forth.c | 2 | ||||
| -rw-r--r-- | plugins/lisp.c | 5 | ||||
| -rw-r--r-- | plugins/regex.c | 5 | ||||
| -rw-r--r-- | plugins/sql.c | 5 | ||||
| -rw-r--r-- | src/codegen/codegen_decl.c | 3 | ||||
| -rw-r--r-- | src/main.c | 17 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 16 | ||||
| -rw-r--r-- | src/parser/parser_utils.c | 35 | ||||
| -rw-r--r-- | src/plugins/plugin_manager.c | 11 | ||||
| -rw-r--r-- | src/plugins/plugin_manager.h | 5 |
14 files changed, 270 insertions, 37 deletions
@@ -27,13 +27,7 @@ SRCS = src/main.c \ src/lsp/lsp_index.c \ src/zen/zen_facts.c \ src/repl/repl.c \ - src/plugins/plugin_manager.c \ - plugins/befunge.c \ - plugins/brainfuck.c \ - plugins/forth.c \ - plugins/lisp.c \ - plugins/regex.c \ - plugins/sql.c + src/plugins/plugin_manager.c OBJ_DIR = obj OBJS = $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRCS)) @@ -43,9 +37,16 @@ PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin MANDIR = $(PREFIX)/share/man SHAREDIR = $(PREFIX)/share/zenc +INCLUDEDIR = $(PREFIX)/include/zenc + +PLUGINS = plugins/befunge.so plugins/brainfuck.so plugins/forth.so plugins/lisp.so plugins/regex.so plugins/sql.so # Default target -all: $(TARGET) +all: $(TARGET) $(PLUGINS) + +# Build plugins +plugins/%.so: plugins/%.c + $(CC) $(CFLAGS) -shared -fPIC -o $@ $< # Link $(TARGET): $(OBJS) @@ -72,6 +73,10 @@ install: $(TARGET) # Install standard library install -d $(SHAREDIR) cp -r std $(SHAREDIR)/ + + # Install plugin headers + install -d $(INCLUDEDIR) + install -m 644 plugins/zprep_plugin.h $(INCLUDEDIR)/zprep_plugin.h @echo "=> Installed to $(BINDIR)/$(TARGET)" @echo "=> Man pages installed to $(MANDIR)" @echo "=> Standard library installed to $(SHAREDIR)/std" @@ -89,7 +94,7 @@ uninstall: # Clean clean: - rm -rf $(OBJ_DIR) $(TARGET) out.c + rm -rf $(OBJ_DIR) $(TARGET) out.c plugins/*.so @echo "=> Clean complete!" # Test diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md new file mode 100644 index 0000000..ea21485 --- /dev/null +++ b/docs/PLUGINS.md @@ -0,0 +1,170 @@ +# Zen C Plugin System Guide + +Zen C offers a plugin system that allows you to extend the language syntax and transpilation process. Plugins act as compile-time hooks that can parse arbitrary text blocks and generate C code. + +## Quick Start + +### Using a Plugin + +To use a plugin, import it using the `import plugin` syntax: + +```zc +import plugin "regex" as re +// or simple: import plugin "regex" (uses "regex" as identifier) + +fn main() { + var valid = re! { ^[a-z]+$ }; +} +``` + +The syntax `alias! { ... }` invokes the plugin. The content inside the braces is passed as a raw string to the plugin's transpiler function. + +## Creating a Plugin + +Plugins are written in C and compiled into shared objects (or built-in to the compiler). + +### API Reference (`zprep_plugin.h`) + +The core API is simple. A plugin exposes a `ZPlugin` struct containing its name and a transpiler function. + +```c +typedef struct { + // -> Context information + const char *filename; // Current file being compiled + int current_line; // Line number of the invocation + FILE *out; // Output stream (injects code at call site) + FILE *hoist_out; // Hoisted output (injects code at file scope) +} ZApi; + +// -> The transpiler function +// input_body: The raw text inside the plugin block { ... } +// api: Access to transpiler context and output streams +typedef void (*ZPluginTranspileFn)(const char *input_body, const ZApi *api); + +typedef struct { + char name[32]; + ZPluginTranspileFn fn; +} ZPlugin; +``` + +### Writing a Brainfuck Plugin + +Let's implement a plugin that compiles Brainfuck code directly into C logic at compile time. + +#### 1. Include the API + +```c +#include "zprep_plugin.h" +``` + +#### 2. Implement the transpiler + +The transpiler function reads the Brainfuck source code and writes equivalent C code to `api->out`. + +```c +void bf_transpile(const char *input_body, const ZApi *api) +{ + FILE *out = api->out; + + // Initialize tape and pointer in a local block. + fprintf(out, "{\n"); + fprintf(out, " static unsigned char tape[30000] = {0};\n"); + fprintf(out, " unsigned char *ptr = tape;\n"); + + const char *c = input_body; + while (*c) + { + switch (*c) + { + case '>': fprintf(out, " ++ptr;\n"); break; + case '<': fprintf(out, " --ptr;\n"); break; + case '+': fprintf(out, " ++*ptr;\n"); break; + case '-': fprintf(out, " --*ptr;\n"); break; + case '.': fprintf(out, " putchar(*ptr);\n"); break; + case ',': fprintf(out, " *ptr = getchar();\n"); break; + case '[': fprintf(out, " while (*ptr) {\n"); break; + case ']': fprintf(out, " }\n"); break; + } + c++; + } + fprintf(out, "}\n"); +} +``` + +#### 3. Register the Plugin + +For dynamic plugins, you must export a `z_plugin_init` function that returns a pointer to your `ZPlugin` struct. + +```c +ZPlugin brainfuck_plugin = { + .name = "brainfuck", + .fn = bf_transpile +}; + +// Entry point for the dynamic loader +ZPlugin *z_plugin_init(void) +{ + return &brainfuck_plugin; +} +``` + +### Building the Plugin + +Compile your plugin as a shared object (`.so`). You'll need access to `zprep_plugin.h`. + +```bash +# If building from source repository: +gcc -shared -fPIC -o brainfuck.so brainfuck.c -I./plugins + +# If Zen C is installed system-wide: +gcc -shared -fPIC -o brainfuck.so brainfuck.c -I/usr/local/include/zenc +``` + +### Using the Plugin + +You can import plugins using relative paths or system-wide lookups: + +```zc +// Relative import (finds .so relative to this file) +import plugin "./brainfuck.so" as bf + +// System import (finds .so in CWD or library path) +import plugin "brainfuck.so" as bf2 + +fn main() { + bf! { + ++++++++++[>+++++++>++++++++++>+++<<<-]>++. + } +} +``` + +### Output example + +When you write: + +```zc +bf! { + ++++++++++[>+++++++>++++++++++>+++<<<-]>++. +} +``` + +The plugin generates: + +```c +{ + static unsigned char tape[30000] = {0}; + unsigned char *ptr = tape; + ++*ptr; + ++*ptr; + // ... (logic for + and [ ) + while (*ptr) { + ++ptr; + // ... + } + ptr++; + // ... + putchar(*ptr); +} +``` + +This C code is then compiled by the backend, resulting in a highly optimized native binary from your DSL! diff --git a/plugins/befunge.c b/plugins/befunge.c index 92165f4..fecfdb5 100644 --- a/plugins/befunge.c +++ b/plugins/befunge.c @@ -197,3 +197,8 @@ void befunge_transpile(const char *input_body, const ZApi *api) } ZPlugin befunge_plugin = {.name = "befunge", .fn = befunge_transpile}; + +ZPlugin *z_plugin_init(void) +{ + return &befunge_plugin; +} diff --git a/plugins/brainfuck.c b/plugins/brainfuck.c index 576029b..67758c3 100644 --- a/plugins/brainfuck.c +++ b/plugins/brainfuck.c @@ -42,3 +42,8 @@ void bf_transpile(const char *input_body, const ZApi *api) } ZPlugin brainfuck_plugin = {.name = "brainfuck", .fn = bf_transpile}; + +ZPlugin *z_plugin_init(void) +{ + return &brainfuck_plugin; +} diff --git a/plugins/forth.c b/plugins/forth.c index 90e05c6..f79fecc 100644 --- a/plugins/forth.c +++ b/plugins/forth.c @@ -631,7 +631,7 @@ void zprep_forth_plugin_fn(const char *input_body, const ZApi *api) ZPlugin forth_plugin = {.name = "forth", .fn = zprep_forth_plugin_fn}; -ZPlugin *zprep_plugin_init() +ZPlugin *z_plugin_init() { return &forth_plugin; } diff --git a/plugins/lisp.c b/plugins/lisp.c index 439dd16..30268bb 100644 --- a/plugins/lisp.c +++ b/plugins/lisp.c @@ -488,3 +488,8 @@ void lisp_transpile(const char *input_body, const ZApi *api) } ZPlugin lisp_plugin = {.name = "lisp", .fn = lisp_transpile}; + +ZPlugin *z_plugin_init(void) +{ + return &lisp_plugin; +} diff --git a/plugins/regex.c b/plugins/regex.c index fb63986..a207832 100644 --- a/plugins/regex.c +++ b/plugins/regex.c @@ -224,3 +224,8 @@ static void emit_match_logic(const char *pattern, FILE *out) } ZPlugin regex_plugin = {.name = "regex", .fn = regex_transpile}; + +ZPlugin *z_plugin_init(void) +{ + return ®ex_plugin; +} diff --git a/plugins/sql.c b/plugins/sql.c index 130317f..dedb289 100644 --- a/plugins/sql.c +++ b/plugins/sql.c @@ -413,3 +413,8 @@ void sql_transpile(const char *input_body, const ZApi *api) } ZPlugin sql_plugin = {.name = "sql", .fn = sql_transpile}; + +ZPlugin *z_plugin_init(void) +{ + return &sql_plugin; +} diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 2dff0dd..026824e 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -738,7 +738,8 @@ void emit_impl_vtables(ParserContext *ctx, FILE *out) while (m) { const char *orig = parse_original_method_name(m->func.name); - fprintf(out, ".%s = (__typeof__(((%s_VTable*)0)->%s))%s_%s_%s", orig, trait, orig, strct, trait, orig); + fprintf(out, ".%s = (__typeof__(((%s_VTable*)0)->%s))%s_%s_%s", orig, trait, orig, + strct, trait, orig); if (m->next) { fprintf(out, ", "); @@ -125,7 +125,7 @@ int main(int argc, char **argv) { g_config.emit_c = 1; } - else if (strcmp(arg, "--version") == 0|| strcmp(arg, "-V") == 0) + else if (strcmp(arg, "--version") == 0 || strcmp(arg, "-V") == 0) { print_version(); return 0; @@ -228,21 +228,6 @@ int main(int argc, char **argv) // Scan for build directives (e.g. //> link: -lm) scan_build_directives(&ctx, src); - // Register built-in plugins - extern ZPlugin brainfuck_plugin; - extern ZPlugin befunge_plugin; - extern ZPlugin lisp_plugin; - extern ZPlugin forth_plugin; - extern ZPlugin regex_plugin; - extern ZPlugin sql_plugin; - - zptr_register_plugin(&brainfuck_plugin); - zptr_register_plugin(&befunge_plugin); - zptr_register_plugin(&lisp_plugin); - zptr_register_plugin(&forth_plugin); - zptr_register_plugin(®ex_plugin); - zptr_register_plugin(&sql_plugin); - Lexer l; lexer_init(&l, src); diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 227b5aa..24dfa10 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -3551,6 +3551,22 @@ ASTNode *parse_import(ParserContext *ctx, Lexer *l) strncpy(plugin_name, plugin_tok.start + 1, name_len); plugin_name[name_len] = '\0'; + if (plugin_name[0] == '.' && + (plugin_name[1] == '/' || (plugin_name[1] == '.' && plugin_name[2] == '/'))) + { + char *current_dir = xstrdup(g_current_filename); + char *last_slash = strrchr(current_dir, '/'); + if (last_slash) + { + *last_slash = 0; + char resolved_path[1024]; + snprintf(resolved_path, sizeof(resolved_path), "%s/%s", current_dir, plugin_name); + free(plugin_name); + plugin_name = xstrdup(resolved_path); + } + free(current_dir); + } + // Check for optional "as alias" char *alias = NULL; Token as_tok = lexer_peek(l); diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 490ea42..2e2fb5b 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -1,5 +1,6 @@ #include "../codegen/codegen.h" +#include "../plugins/plugin_manager.h" #include "parser.h" #include <ctype.h> #include <stdio.h> @@ -2581,8 +2582,40 @@ char *find_similar_symbol(ParserContext *ctx, const char *name) void register_plugin(ParserContext *ctx, const char *name, const char *alias) { + // Try to find existing (built-in) or already loaded plugin + ZPlugin *plugin = zptr_find_plugin(name); + + // If not found, try to load it dynamically + if (!plugin) + { + plugin = zptr_load_plugin(name); + + if (!plugin) + { + char path[1024]; + snprintf(path, sizeof(path), "%s.so", name); + plugin = zptr_load_plugin(path); + } + + if (!plugin && !strchr(name, '/')) + { + char path[1024]; + snprintf(path, sizeof(path), "./%s.so", name); + plugin = zptr_load_plugin(path); + } + } + + if (!plugin) + { + fprintf(stderr, + COLOR_RED "Error:" COLOR_RESET " Could not load plugin '%s'\n" + " Tried built-ins and dynamic loading (.so)\n", + name); + exit(1); + } + ImportedPlugin *p = xmalloc(sizeof(ImportedPlugin)); - p->name = xstrdup(name); + p->name = xstrdup(plugin->name); // Use the plugin's internal name p->alias = alias ? xstrdup(alias) : NULL; p->next = ctx->imported_plugins; ctx->imported_plugins = p; diff --git a/src/plugins/plugin_manager.c b/src/plugins/plugin_manager.c index 12c85cd..afca55e 100644 --- a/src/plugins/plugin_manager.c +++ b/src/plugins/plugin_manager.c @@ -39,13 +39,12 @@ void zptr_register_plugin(ZPlugin *plugin) head = node; } -int zptr_load_plugin(const char *path) +ZPlugin *zptr_load_plugin(const char *path) { void *handle = dlopen(path, RTLD_LAZY); if (!handle) { - fprintf(stderr, "Failed to load plugin '%s': %s\n", path, dlerror()); - return 0; + return NULL; } ZPluginInitFn init_fn = (ZPluginInitFn)dlsym(handle, "z_plugin_init"); @@ -53,7 +52,7 @@ int zptr_load_plugin(const char *path) { fprintf(stderr, "Plugin '%s' missing 'z_plugin_init' symbol\n", path); dlclose(handle); - return 0; + return NULL; } ZPlugin *plugin = init_fn(); @@ -61,7 +60,7 @@ int zptr_load_plugin(const char *path) { fprintf(stderr, "Plugin '%s' init returned NULL\n", path); dlclose(handle); - return 0; + return NULL; } // Register @@ -71,7 +70,7 @@ int zptr_load_plugin(const char *path) node->next = head; head = node; - return 1; + return plugin; } ZPlugin *zptr_find_plugin(const char *name) diff --git a/src/plugins/plugin_manager.h b/src/plugins/plugin_manager.h index a1e8ca6..9b35c2d 100644 --- a/src/plugins/plugin_manager.h +++ b/src/plugins/plugin_manager.h @@ -10,9 +10,8 @@ void zptr_plugin_mgr_init(void); void zptr_register_plugin(ZPlugin *plugin); // Load a plugin from a shared object file (.so). -// Returns 1 on success, 0 on failure. -// Yeah, for now, I'm sorry Windows guys. -int zptr_load_plugin(const char *path); +// Returns ZPlugin pointer on success, NULL on failure. +ZPlugin *zptr_load_plugin(const char *path); // Find a registered plugin by name. ZPlugin *zptr_find_plugin(const char *name); |
