summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile23
-rw-r--r--docs/PLUGINS.md170
-rw-r--r--plugins/befunge.c5
-rw-r--r--plugins/brainfuck.c5
-rw-r--r--plugins/forth.c2
-rw-r--r--plugins/lisp.c5
-rw-r--r--plugins/regex.c5
-rw-r--r--plugins/sql.c5
-rw-r--r--src/codegen/codegen_decl.c3
-rw-r--r--src/main.c17
-rw-r--r--src/parser/parser_stmt.c16
-rw-r--r--src/parser/parser_utils.c35
-rw-r--r--src/plugins/plugin_manager.c11
-rw-r--r--src/plugins/plugin_manager.h5
14 files changed, 270 insertions, 37 deletions
diff --git a/Makefile b/Makefile
index c83cd0c..82d75b8 100644
--- a/Makefile
+++ b/Makefile
@@ -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 &regex_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, ", ");
diff --git a/src/main.c b/src/main.c
index 49691ea..2c33a6b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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(&regex_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);