summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorczjstmax <maxwasmailed@proton.me>2026-01-31 20:09:51 +0100
committerGitHub <noreply@github.com>2026-01-31 20:09:51 +0100
commit5f283b75488e89d2c4f261ae83e0424daec29554 (patch)
treeba1637d3885213095b312f81a477c33b1ebca6aa
parentd2e2617dec584884b92eb452f377b20c0bf8f321 (diff)
parent13af49ba93d653fb6306604889c4ef66e9018873 (diff)
Merge branch 'z-libs:main' into main
-rw-r--r--docs/std/slice.md15
-rw-r--r--src/codegen/codegen.c214
-rw-r--r--src/codegen/codegen_decl.c1
-rw-r--r--src/codegen/codegen_main.c33
-rw-r--r--src/parser/parser_expr.c10
-rw-r--r--src/parser/parser_stmt.c114
-rw-r--r--src/parser/parser_struct.c115
-rw-r--r--std.zc1
-rw-r--r--std/mem.zc23
-rw-r--r--std/regex.zc198
-rw-r--r--std/slice.zc9
-rw-r--r--tests/memory/test_memory_safety.zc9
-rw-r--r--tests/std/test_regex.zc187
13 files changed, 766 insertions, 163 deletions
diff --git a/docs/std/slice.md b/docs/std/slice.md
index b70c5fe..f029995 100644
--- a/docs/std/slice.md
+++ b/docs/std/slice.md
@@ -10,12 +10,12 @@ import "std/slice.zc"
fn main() {
let arr: int[5] = [1, 2, 3, 4, 5];
- // Direct iteration (Recommended)
+ // Direct iteration (auto-imports std/slice.zc)
for val in arr {
println "{val}";
}
- // Manual slice creation (for partial views or specific needs)
+ // Manual slice creation
let slice = Slice<int>::from_array((int*)(&arr), 5);
for val in slice {
println "{val}";
@@ -39,6 +39,7 @@ struct Slice<T> {
| Method | Signature | Description |
| :--- | :--- | :--- |
| **from_array** | `Slice<T>::from_array(arr: T*, len: usize) -> Slice<T>` | Creates a slice view over an array. |
+| **new** | `Slice<T>::new(data: T*, len: usize) -> Slice<T>` | Alias for `from_array` (backwards compat). |
### Iteration
@@ -62,10 +63,10 @@ struct Slice<T> {
### Iterating over fixed-size arrays
```zc
+// std/slice.zc is auto-imported when using for-in on arrays
let numbers: int[3] = [10, 20, 30];
-let slice = Slice<int>::from_array((int*)(&numbers), 3);
-for n in slice {
+for n in numbers {
println "Number: {n}";
}
```
@@ -73,6 +74,8 @@ for n in slice {
### Safe indexed access
```zc
+import "std/slice.zc"
+
let arr: int[3] = [1, 2, 3];
let slice = Slice<int>::from_array((int*)(&arr), 3);
@@ -84,7 +87,7 @@ if (!opt.is_none()) {
## Notes
-- `Slice<T>` does not own its data - it's just a view
+- `Slice<T>` does not own its data — it's just a view
- No memory management needed (no `free()` method)
-- Must specify the generic type explicitly: `Slice<int>`, `Slice<String>`, etc.
+- **Auto-import**: `std/slice.zc` is automatically imported when using `for val in arr` on a fixed-size array
- The array pointer cast `(T*)(&arr)` is required for fixed-size arrays
diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c
index 7a67428..37415c2 100644
--- a/src/codegen/codegen.c
+++ b/src/codegen/codegen.c
@@ -1,4 +1,3 @@
-
#include "codegen.h"
#include "zprep.h"
#include "../constants.h"
@@ -59,9 +58,9 @@ static void codegen_var_expr(ParserContext *ctx, ASTNode *node, FILE *out)
if (node->var_ref.suggestion && !ctx->silent_warnings)
{
char msg[256];
- sprintf(msg, "Undefined variable '%s'", node->var_ref.name);
char help[256];
- sprintf(help, "Did you mean '%s'?", node->var_ref.suggestion);
+ snprintf(msg, sizeof(msg), "Undefined variable '%s'", node->var_ref.name);
+ snprintf(help, sizeof(help), "Did you mean '%s'?", node->var_ref.suggestion);
zwarn_at(node->token, "%s\n = help: %s", msg, help);
}
}
@@ -192,7 +191,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
else if ((strcmp(node->binary.op, "==") == 0 || strcmp(node->binary.op, "!=") == 0))
{
char *t1 = infer_type(ctx, node->binary.left);
-
int is_ptr = 0;
if (t1)
{
@@ -207,19 +205,16 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
int resolved = 0;
ASTNode *alias = global_user_structs;
- if (alias)
+ while (alias)
{
- while (alias)
+ if (alias->type == NODE_TYPE_ALIAS &&
+ strcmp(check, alias->type_alias.alias) == 0)
{
- if (alias->type == NODE_TYPE_ALIAS &&
- strcmp(check, alias->type_alias.alias) == 0)
- {
- check = alias->type_alias.original_type;
- resolved = 1;
- break;
- }
- alias = alias->next;
+ check = alias->type_alias.original_type;
+ resolved = 1;
+ break;
}
+ alias = alias->next;
}
if (!resolved)
{
@@ -229,10 +224,9 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
int is_basic = IS_BASIC_TYPE(t1);
-
ASTNode *def = t1 ? find_struct_def(ctx, t1) : NULL;
- if (t1 && def && (def->type == NODE_STRUCT || def->type == NODE_ENUM) && !is_basic &&
- !is_ptr)
+
+ if (t1 && def && (def->type == NODE_STRUCT || def->type == NODE_ENUM) && !is_basic && !is_ptr)
{
char *base = t1;
if (strncmp(base, "struct ", 7) == 0)
@@ -285,8 +279,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
else if (t1 && (strcmp(t1, "string") == 0 || strcmp(t1, "char*") == 0 ||
strcmp(t1, "const char*") == 0))
{
- // Check if comparing to NULL - don't use strcmp for NULL comparisons
- char *t2 = infer_type(ctx, node->binary.right);
int is_null_compare = 0;
if (node->binary.right->type == NODE_EXPR_VAR &&
strcmp(node->binary.right->var_ref.name, "NULL") == 0)
@@ -298,9 +290,28 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
{
is_null_compare = 1;
}
+ else if (node->binary.right->type == NODE_EXPR_LITERAL &&
+ node->binary.right->literal.type_kind == LITERAL_INT &&
+ node->binary.right->literal.int_val == 0)
+ {
+ is_null_compare = 1;
+ }
+ else if (node->binary.left->type == NODE_EXPR_LITERAL &&
+ node->binary.left->literal.type_kind == LITERAL_INT &&
+ node->binary.left->literal.int_val == 0)
+ {
+ is_null_compare = 1;
+ }
- if (!is_null_compare && strcmp(t1, "string") == 0 && t2 &&
- strcmp(t2, "string") == 0)
+ if (is_null_compare)
+ {
+ fprintf(out, "(");
+ codegen_expression(ctx, node->binary.left, out);
+ fprintf(out, " %s ", node->binary.op);
+ codegen_expression(ctx, node->binary.right, out);
+ fprintf(out, ")");
+ }
+ else
{
fprintf(out, "(strcmp(");
codegen_expression(ctx, node->binary.left, out);
@@ -315,19 +326,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
fprintf(out, ") != 0)");
}
}
- else
- {
- // Direct pointer comparison
- fprintf(out, "(");
- codegen_expression(ctx, node->binary.left, out);
- fprintf(out, " %s ", node->binary.op);
- codegen_expression(ctx, node->binary.right, out);
- fprintf(out, ")");
- }
- if (t2)
- {
- free(t2);
- }
}
else
{
@@ -337,6 +335,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
codegen_expression(ctx, node->binary.right, out);
fprintf(out, ")");
}
+ if (t1) free(t1);
}
else
{
@@ -394,14 +393,13 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
// Check for Static Enum Variant Call: Enum.Variant(...)
-
if (target->type == NODE_EXPR_VAR)
{
ASTNode *def = find_struct_def(ctx, target->var_ref.name);
if (def && def->type == NODE_ENUM)
{
char mangled[256];
- sprintf(mangled, "%s_%s", target->var_ref.name, method);
+ snprintf(mangled, sizeof(mangled), "%s_%s", target->var_ref.name, method);
FuncSig *sig = find_func(ctx, mangled);
if (sig)
{
@@ -410,15 +408,13 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
int arg_idx = 0;
while (arg)
{
- if (arg_idx > 0 && arg)
+ if (arg_idx > 0)
{
fprintf(out, ", ");
}
- Type *param_t =
- (arg_idx < sig->total_args) ? sig->arg_types[arg_idx] : NULL;
+ Type *param_t = (arg_idx < sig->total_args) ? sig->arg_types[arg_idx] : NULL;
- // Tuple Packing Logic
if (param_t && param_t->kind == TYPE_STRUCT &&
strncmp(param_t->name, "Tuple_", 6) == 0 && sig->total_args == 1 &&
node->call.arg_count > 1)
@@ -436,7 +432,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
arg = arg->next;
}
fprintf(out, "}");
- break; // All args consumed
+ break;
}
codegen_expression(ctx, arg, out);
@@ -456,7 +452,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
char *ptr = strchr(clean, '*');
if (ptr)
{
- *ptr = 0;
+ *ptr = '\0';
}
char *base = clean;
@@ -518,33 +514,30 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
int need_cast = 0;
char mixin_func_name[128];
- sprintf(mixin_func_name, "%s__%s", call_base, method);
+ snprintf(mixin_func_name, sizeof(mixin_func_name), "%s__%s", call_base, method);
char *resolved_method_suffix = NULL;
if (!find_func(ctx, mixin_func_name))
{
- // Try resolving as a trait method: Struct__Trait_Method
StructRef *ref = ctx->parsed_impls_list;
while (ref)
{
- if (ref->node && ref->node->type == NODE_IMPL_TRAIT)
+ if (ref->node && ref->node->type == NODE_IMPL_TRAIT &&
+ strcmp(ref->node->impl_trait.target_type, base) == 0)
{
- if (strcmp(ref->node->impl_trait.target_type, base) == 0)
+ char trait_mangled[256];
+ snprintf(trait_mangled, sizeof(trait_mangled), "%s__%s_%s", base,
+ ref->node->impl_trait.trait_name, method);
+ if (find_func(ctx, trait_mangled))
{
- char trait_mangled[256];
- sprintf(trait_mangled, "%s__%s_%s", base,
- ref->node->impl_trait.trait_name, method);
- if (find_func(ctx, trait_mangled))
- {
- char *suffix =
- xmalloc(strlen(ref->node->impl_trait.trait_name) +
- strlen(method) + 2);
- sprintf(suffix, "%s_%s", ref->node->impl_trait.trait_name,
- method);
- resolved_method_suffix = suffix;
- break;
- }
+ size_t suffix_len = strlen(ref->node->impl_trait.trait_name) +
+ strlen(method) + 2;
+ char *suffix = xmalloc(suffix_len);
+ snprintf(suffix, suffix_len, "%s_%s",
+ ref->node->impl_trait.trait_name, method);
+ resolved_method_suffix = suffix;
+ break;
}
}
ref = ref->next;
@@ -559,15 +552,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
if (it->impl_node && it->impl_node->type == NODE_IMPL_TRAIT)
{
tname = it->impl_node->impl_trait.trait_name;
- }
- if (tname)
- {
char trait_mangled[512];
- sprintf(trait_mangled, "%s__%s_%s", base, tname, method);
+ snprintf(trait_mangled, sizeof(trait_mangled),
+ "%s__%s_%s", base, tname, method);
if (find_func(ctx, trait_mangled))
{
- char *suffix = xmalloc(strlen(tname) + strlen(method) + 2);
- sprintf(suffix, "%s_%s", tname, method);
+ size_t suffix_len = strlen(tname) + strlen(method) + 2;
+ char *suffix = xmalloc(suffix_len);
+ snprintf(suffix, suffix_len, "%s_%s", tname, method);
resolved_method_suffix = suffix;
break;
}
@@ -582,15 +574,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
else
{
- // Method not found on primary struct, check mixins
ASTNode *def = find_struct_def(ctx, base);
if (def && def->type == NODE_STRUCT && def->strct.used_structs)
{
for (int k = 0; k < def->strct.used_struct_count; k++)
{
char mixin_check[128];
- sprintf(mixin_check, "%s__%s", def->strct.used_structs[k],
- method);
+ snprintf(mixin_check, sizeof(mixin_check), "%s__%s",
+ def->strct.used_structs[k], method);
if (find_func(ctx, mixin_check))
{
call_base = def->strct.used_structs[k];
@@ -620,11 +611,19 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
arg = arg->next;
}
fprintf(out, ")");
+
+ if (resolved_method_suffix)
+ {
+ free(resolved_method_suffix);
+ }
}
free(clean);
+ free(type);
return;
}
+ if (type) free(type);
}
+
if (node->call.callee->type == NODE_EXPR_VAR)
{
ASTNode *def = find_struct_def(ctx, node->call.callee->var_ref.name);
@@ -680,26 +679,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
if (node->call.arg_names && node->call.callee->type == NODE_EXPR_VAR)
{
- char *fn_name = node->call.callee->var_ref.name;
- FuncSig *sig = find_func(ctx, fn_name);
-
- if (sig && sig->arg_types)
- {
- for (int p = 0; p < sig->total_args; p++)
- {
- ASTNode *arg = node->call.args;
-
- for (int i = 0; i < node->call.arg_count && arg; i++, arg = arg->next)
- {
- if (node->call.arg_names[i] && p < node->call.arg_count)
- {
-
- // For now, emit in order provided...
- }
- }
- }
- }
-
ASTNode *arg = node->call.args;
int first = 1;
while (arg)
@@ -746,11 +725,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
strncmp(param_t->name, "Tuple_", 6) == 0 && sig->total_args == 1 &&
node->call.arg_count > 1)
{
- // Implicit Tuple Packing:
- // Function expects 1 Tuple argument, but call has multiple args -> Pack
- // them
fprintf(out, "(%s){", param_t->name);
-
ASTNode *curr = arg;
int first_field = 1;
while (curr)
@@ -765,8 +740,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
fprintf(out, "}");
handled = 1;
-
- // Advance main loop iterator to end
arg = NULL;
}
}
@@ -775,7 +748,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
{
if (arg == NULL)
{
- break; // Tuple packed all args
+ break;
}
}
else
@@ -800,16 +773,12 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
case NODE_EXPR_MEMBER:
if (strcmp(node->member.field, "len") == 0)
{
- if (node->member.target->type_info)
+ if (node->member.target->type_info &&
+ node->member.target->type_info->kind == TYPE_ARRAY &&
+ node->member.target->type_info->array_size > 0)
{
- if (node->member.target->type_info->kind == TYPE_ARRAY)
- {
- if (node->member.target->type_info->array_size > 0)
- {
- fprintf(out, "%d", node->member.target->type_info->array_size);
- break;
- }
- }
+ fprintf(out, "%d", node->member.target->type_info->array_size);
+ break;
}
}
@@ -831,26 +800,15 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
else
{
- if (node->member.target->type == NODE_EXPR_CAST)
- {
- fprintf(out, "(");
- }
codegen_expression(ctx, node->member.target, out);
- if (node->member.target->type == NODE_EXPR_CAST)
- {
- fprintf(out, ")");
- }
- // Verify actual type instead of trusting is_pointer_access flag
char *lt = infer_type(ctx, node->member.target);
int actually_ptr = 0;
if (lt && (lt[strlen(lt) - 1] == '*' || strstr(lt, "*")))
{
actually_ptr = 1;
}
- if (lt)
- {
- free(lt);
- }
+ if (lt) free(lt);
+
char *field = node->member.field;
if (field && field[0] >= '0' && field[0] <= '9')
{
@@ -873,7 +831,8 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
is_slice_struct = 1;
}
}
- if (node->index.array->resolved_type)
+
+ if (!is_slice_struct && node->index.array->resolved_type)
{
if (strncmp(node->index.array->resolved_type, "Slice_", 6) == 0)
{
@@ -888,10 +847,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
{
is_slice_struct = 1;
}
- if (inferred)
- {
- free(inferred);
- }
+ if (inferred) free(inferred);
}
if (is_slice_struct)
@@ -990,7 +946,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
}
else
{
- fprintf(out, "/* UNSAFE: Full Slice on unknown size */ 0; ");
+ fprintf(out, "0; ");
}
}
@@ -1006,6 +962,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
fprintf(out, "(Slice_%s){ .data = _arr + _start, .len = _len, .cap = _len }; })",
tname);
}
+ if (tname && strcmp(tname, "unknown") != 0) free(tname);
break;
}
case NODE_BLOCK:
@@ -1096,9 +1053,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
break;
case NODE_PLUGIN:
{
- // Plugin registry - declare external plugins
ZPlugin *found = zptr_find_plugin(node->plugin_stmt.plugin_name);
-
if (found)
{
ZApi api = {.filename = g_current_filename ? g_current_filename : "input.zc",
@@ -1178,9 +1133,9 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
break;
}
case NODE_EXPR_CAST:
- fprintf(out, "(%s)(", node->cast.target_type);
+ fprintf(out, "((%s)(", node->cast.target_type);
codegen_expression(ctx, node->cast.expr, out);
- fprintf(out, ")");
+ fprintf(out, "))");
break;
case NODE_EXPR_SIZEOF:
if (node->size_of.target_type)
@@ -1211,20 +1166,19 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out)
{
Type *t = node->reflection.target_type;
if (node->reflection.kind == 0)
- { // @type_name
+ {
char *s = codegen_type_to_string(t);
fprintf(out, "\"%s\"", s);
free(s);
}
else
- { // @fields
+ {
if (t->kind != TYPE_STRUCT || !t->name)
{
fprintf(out, "((void*)0)");
break;
}
char *sname = t->name;
- // Find definition
ASTNode *def = find_struct_def(ctx, sname);
if (!def)
{
diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c
index 9d23617..31bd2ee 100644
--- a/src/codegen/codegen_decl.c
+++ b/src/codegen/codegen_decl.c
@@ -50,6 +50,7 @@ void emit_preamble(ParserContext *ctx, FILE *out)
else
{
// Standard hosted preamble.
+ fputs("#define _GNU_SOURCE\n", out);
fputs("#include <stdio.h>\n#include <stdlib.h>\n#include "
"<stddef.h>\n#include <string.h>\n",
out);
diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c
index b298700..82fc3ce 100644
--- a/src/codegen/codegen_main.c
+++ b/src/codegen/codegen_main.c
@@ -448,6 +448,39 @@ void codegen_node(ParserContext *ctx, ASTNode *node, FILE *out)
emit_type_aliases(kids, out); // Emit local aliases (redundant but safe)
emit_trait_defs(kids, out);
+ // Also emit traits from parsed_globals_list (from auto-imported files like std/mem.zc)
+ // but only if they weren't already emitted from kids
+ StructRef *trait_ref = ctx->parsed_globals_list;
+ while (trait_ref)
+ {
+ if (trait_ref->node && trait_ref->node->type == NODE_TRAIT)
+ {
+ // Check if this trait was already in kids (explicitly imported)
+ int already_in_kids = 0;
+ ASTNode *k = kids;
+ while (k)
+ {
+ if (k->type == NODE_TRAIT && k->trait.name && trait_ref->node->trait.name &&
+ strcmp(k->trait.name, trait_ref->node->trait.name) == 0)
+ {
+ already_in_kids = 1;
+ break;
+ }
+ k = k->next;
+ }
+
+ if (!already_in_kids)
+ {
+ // Create a temporary single-node list for emit_trait_defs
+ ASTNode *saved_next = trait_ref->node->next;
+ trait_ref->node->next = NULL;
+ emit_trait_defs(trait_ref->node, out);
+ trait_ref->node->next = saved_next;
+ }
+ }
+ trait_ref = trait_ref->next;
+ }
+
// Track emitted raw statements to prevent duplicates
EmittedContent *emitted_raw = NULL;
diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c
index 28dc465..7c53d96 100644
--- a/src/parser/parser_expr.c
+++ b/src/parser/parser_expr.c
@@ -5644,7 +5644,17 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec)
char *t1 = type_to_string(lhs->type_info);
char *t2 = type_to_string(rhs->type_info);
// Skip type check if either operand is void* (escape hatch type)
+ // or if either operand is a generic type parameter (T, K, V, etc.)
int skip_check = (strcmp(t1, "void*") == 0 || strcmp(t2, "void*") == 0);
+ if (lhs->type_info->kind == TYPE_GENERIC || rhs->type_info->kind == TYPE_GENERIC)
+ {
+ skip_check = 1;
+ }
+ // Also check if type name is a single uppercase letter (common generic param)
+ if ((strlen(t1) == 1 && isupper(t1[0])) || (strlen(t2) == 1 && isupper(t2[0])))
+ {
+ skip_check = 1;
+ }
// Allow comparing pointers/strings with integer literal 0 (NULL)
if (!skip_check)
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
index 7758ae3..0677cf5 100644
--- a/src/parser/parser_stmt.c
+++ b/src/parser/parser_stmt.c
@@ -14,6 +14,118 @@
char *curr_func_ret = NULL;
char *run_comptime_block(ParserContext *ctx, Lexer *l);
+extern char *g_current_filename;
+
+/**
+ * @brief Auto-imports std/slice.zc if not already imported.
+ *
+ * This is called when array iteration is detected in for-in loops,
+ * to ensure the Slice<T>, SliceIter<T>, and Option<T> templates are available.
+ */
+static void auto_import_std_slice(ParserContext *ctx)
+{
+ // Check if already imported via templates
+ GenericTemplate *t = ctx->templates;
+ while (t)
+ {
+ if (strcmp(t->name, "Slice") == 0)
+ {
+ return; // Already have the Slice template
+ }
+ t = t->next;
+ }
+
+ // Try to find and import std/slice.zc
+ static const char *std_paths[] = {"std/slice.zc", "./std/slice.zc", NULL};
+ static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL};
+
+ char resolved_path[1024];
+ int found = 0;
+
+ // First, try relative to current file
+ if (g_current_filename)
+ {
+ char *current_dir = xstrdup(g_current_filename);
+ char *last_slash = strrchr(current_dir, '/');
+ if (last_slash)
+ {
+ *last_slash = 0;
+ snprintf(resolved_path, sizeof(resolved_path), "%s/std/slice.zc", current_dir);
+ if (access(resolved_path, R_OK) == 0)
+ {
+ found = 1;
+ }
+ }
+ free(current_dir);
+ }
+
+ // Try relative paths
+ if (!found)
+ {
+ for (int i = 0; std_paths[i] && !found; i++)
+ {
+ if (access(std_paths[i], R_OK) == 0)
+ {
+ strncpy(resolved_path, std_paths[i], sizeof(resolved_path) - 1);
+ resolved_path[sizeof(resolved_path) - 1] = '\0';
+ found = 1;
+ }
+ }
+ }
+
+ // Try system paths
+ if (!found)
+ {
+ for (int i = 0; system_paths[i] && !found; i++)
+ {
+ snprintf(resolved_path, sizeof(resolved_path), "%s/std/slice.zc", system_paths[i]);
+ if (access(resolved_path, R_OK) == 0)
+ {
+ found = 1;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ return; // Could not find std/slice.zc, instantiate_generic will error
+ }
+
+ // Canonicalize path
+ char *real_fn = realpath(resolved_path, NULL);
+ if (real_fn)
+ {
+ strncpy(resolved_path, real_fn, sizeof(resolved_path) - 1);
+ resolved_path[sizeof(resolved_path) - 1] = '\0';
+ free(real_fn);
+ }
+
+ // Check if already imported
+ if (is_file_imported(ctx, resolved_path))
+ {
+ return;
+ }
+ mark_file_imported(ctx, resolved_path);
+
+ // Load and parse the file
+ char *src = load_file(resolved_path);
+ if (!src)
+ {
+ return; // Could not load file
+ }
+
+ Lexer i;
+ lexer_init(&i, src);
+
+ // Save and restore filename context
+ char *saved_fn = g_current_filename;
+ g_current_filename = resolved_path;
+
+ // Parse the slice module contents
+ parse_program_nodes(ctx, &i);
+
+ g_current_filename = saved_fn;
+}
static void check_assignment_condition(ASTNode *cond)
{
@@ -1193,6 +1305,8 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l)
// Manually trigger generic instantiation for Slice<T>
// This ensures that Slice_int, Slice_float, etc. structures are generated
+ // First, ensure std/slice.zc is imported (auto-import if needed)
+ auto_import_std_slice(ctx);
Token dummy_tok = {0};
instantiate_generic(ctx, "Slice", elem_type_str, elem_type_str, dummy_tok);
diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c
index 109eeee..e53b56c 100644
--- a/src/parser/parser_struct.c
+++ b/src/parser/parser_struct.c
@@ -12,6 +12,114 @@
#include "zprep_plugin.h"
#include "../codegen/codegen.h"
+extern char *g_current_filename;
+
+/**
+ * @brief Auto-imports std/mem.zc if not already imported.
+ *
+ * This is called when the Drop trait is used (impl Drop for X).
+ */
+static void auto_import_std_mem(ParserContext *ctx)
+{
+ // Check if Drop trait is already registered (means mem.zc was imported)
+ if (check_impl(ctx, "Drop", "__trait_marker__"))
+ {
+ // Check_impl returns 0 if not found, but we need a different check
+ // Let's check if we can find any indicator that mem.zc was loaded
+ }
+
+ // Try to find and import std/mem.zc
+ static const char *std_paths[] = {"std/mem.zc", "./std/mem.zc", NULL};
+ static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL};
+
+ char resolved_path[1024];
+ int found = 0;
+
+ // First, try relative to current file
+ if (g_current_filename)
+ {
+ char *current_dir = xstrdup(g_current_filename);
+ char *last_slash = strrchr(current_dir, '/');
+ if (last_slash)
+ {
+ *last_slash = 0;
+ snprintf(resolved_path, sizeof(resolved_path), "%s/std/mem.zc", current_dir);
+ if (access(resolved_path, R_OK) == 0)
+ {
+ found = 1;
+ }
+ }
+ free(current_dir);
+ }
+
+ // Try relative paths
+ if (!found)
+ {
+ for (int i = 0; std_paths[i] && !found; i++)
+ {
+ if (access(std_paths[i], R_OK) == 0)
+ {
+ strncpy(resolved_path, std_paths[i], sizeof(resolved_path) - 1);
+ resolved_path[sizeof(resolved_path) - 1] = '\0';
+ found = 1;
+ }
+ }
+ }
+
+ // Try system paths
+ if (!found)
+ {
+ for (int i = 0; system_paths[i] && !found; i++)
+ {
+ snprintf(resolved_path, sizeof(resolved_path), "%s/std/mem.zc", system_paths[i]);
+ if (access(resolved_path, R_OK) == 0)
+ {
+ found = 1;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ return; // Could not find std/mem.zc
+ }
+
+ // Canonicalize path
+ char *real_fn = realpath(resolved_path, NULL);
+ if (real_fn)
+ {
+ strncpy(resolved_path, real_fn, sizeof(resolved_path) - 1);
+ resolved_path[sizeof(resolved_path) - 1] = '\0';
+ free(real_fn);
+ }
+
+ // Check if already imported
+ if (is_file_imported(ctx, resolved_path))
+ {
+ return;
+ }
+ mark_file_imported(ctx, resolved_path);
+
+ // Load and parse the file
+ char *src = load_file(resolved_path);
+ if (!src)
+ {
+ return; // Could not load file
+ }
+
+ Lexer i;
+ lexer_init(&i, src);
+
+ // Save and restore filename context
+ char *saved_fn = g_current_filename;
+ g_current_filename = resolved_path;
+
+ // Parse the mem module contents
+ parse_program_nodes(ctx, &i);
+
+ g_current_filename = saved_fn;
+}
+
// Trait Parsing
ASTNode *parse_trait(ParserContext *ctx, Lexer *l)
{
@@ -149,6 +257,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l)
}
register_trait(name);
+ add_to_global_list(ctx, n_node); // Track for codegen (VTable emission)
return n_node;
}
@@ -206,6 +315,12 @@ ASTNode *parse_impl(ParserContext *ctx, Lexer *l)
register_generic(ctx, target_gen_param);
}
+ // Auto-import std/mem.zc if implementing Drop, Copy, or Clone traits
+ if (strcmp(name1, "Drop") == 0 || strcmp(name1, "Copy") == 0 || strcmp(name1, "Clone") == 0)
+ {
+ auto_import_std_mem(ctx);
+ }
+
register_impl(ctx, name1, name2);
// RAII: Check for "Drop" trait implementation
diff --git a/std.zc b/std.zc
index 4793c11..3dcc45a 100644
--- a/std.zc
+++ b/std.zc
@@ -18,5 +18,6 @@ import "./std/stack.zc"
import "./std/queue.zc"
import "./std/env.zc"
import "./std/slice.zc"
+import "./std/regex.zc"
import "./std/process.zc"
diff --git a/std/mem.zc b/std/mem.zc
index 6ee96e8..f1a5f5a 100644
--- a/std/mem.zc
+++ b/std/mem.zc
@@ -49,28 +49,7 @@ impl Box<T> {
}
}
-struct Slice<T> {
- data: T*;
- len: usize;
-}
-
-impl Slice<T> {
- fn new(data: T*, len: usize) -> Self {
- return Self { data: data, len: len };
- }
-
- fn get(self, i: usize) -> T {
- return self.data[i];
- }
-
- fn set(self, i: usize, val: T) {
- self.data[i] = val;
- }
-
- fn is_empty(self) -> bool {
- return self.len == 0;
- }
-}
+// Note: Slice<T> is defined in std/slice.zc with iteration support
fn mem_zero<T>(ptr: T*, count: usize) {
memset(ptr, 0, sizeof(T) * count);
diff --git a/std/regex.zc b/std/regex.zc
new file mode 100644
index 0000000..f64b36e
--- /dev/null
+++ b/std/regex.zc
@@ -0,0 +1,198 @@
+include <regex.h>
+
+import "./core.zc"
+import "./string.zc"
+import "./vec.zc"
+import "./option.zc"
+
+struct Match {
+ text: char*;
+ start: int;
+ len: int;
+}
+
+impl Match {
+ fn new(text: char*, start: int, len: int) -> Match {
+ return Match { text: text, start: start, len: len };
+ }
+
+ fn as_string(self) -> char* {
+ return self.text;
+ }
+
+ fn end(self) -> int {
+ return self.start + self.len;
+ }
+}
+
+struct Regex {
+ preg: void*;
+ pattern: char*;
+ flags: int;
+}
+
+impl Regex {
+ fn compile(pattern: char*) -> Regex {
+ return Regex::compile_with_flags(pattern, 1 | 2);
+ }
+
+ fn compile_with_flags(pattern: char*, flags: int) -> Regex {
+ let preg = malloc(1024);
+ let status = regcomp(preg, pattern, flags);
+ if (status != 0) {
+ free(preg);
+ return Regex { preg: 0, pattern: 0, flags: flags };
+ }
+ return Regex { preg: preg, pattern: pattern, flags: flags };
+ }
+
+ fn is_valid(self) -> bool {
+ return self.preg != 0;
+ }
+
+ fn match(self, text: char*) -> bool {
+ if (self.preg == 0) { return false; }
+ return regexec(self.preg, text, 0, 0, 0) == 0;
+ }
+
+ fn match_full(self, text: char*) -> bool {
+ return self.match(text);
+ }
+
+ fn match_at(self, text: char*, offset: int) -> bool {
+ if (self.preg == 0) { return false; }
+ let len = strlen(text);
+ if (offset < 0 || offset > len) { return false; }
+ return regexec(self.preg, text + offset, 0, 0, 0) == 0;
+ }
+
+ fn is_match(self, text: char*) -> bool {
+ return self.match(text);
+ }
+
+ fn find(self, text: char*) -> Option<Match> {
+ if (self.preg == 0) { return Option<Match>::None(); }
+ let t_len = strlen(text);
+ for (let i = 0; i <= t_len; i = i + 1) {
+ let sub = text + i;
+ if (regexec(self.preg, sub, 0, 0, 0) == 0) {
+ let j = 0;
+ while (text[i + j] != 0 && regexec(self.preg, sub, 0, 0, 0) == 0) {
+ j = j + 1;
+ sub = text + i + j;
+ }
+ return Option<Match>::Some(Match::new(text + i, i, j));
+ }
+ }
+ return Option<Match>::None();
+ }
+
+ fn find_at(self, text: char*, start: int) -> Option<Match> {
+ let len = strlen(text);
+ if (start < 0 || start >= len) {
+ return Option<Match>::None();
+ }
+ return self.find(text + start);
+ }
+
+ fn count(self, text: char*) -> int {
+ if (self.preg == 0) { return 0; }
+ let count = 0;
+ let pos = 0;
+ let t_len = strlen(text);
+ while (pos < t_len) {
+ let sub = text + pos;
+ if (regexec(self.preg, sub, 0, 0, 0) == 0) {
+ count = count + 1;
+ pos = pos + 1;
+ } else {
+ break;
+ }
+ }
+ return count;
+ }
+
+ fn split(self, text: char*) -> Vec<String> {
+ let parts = Vec<String>::new();
+ if (self.preg == 0) {
+ parts.push(String::from(text));
+ return parts;
+ }
+ let t_len = strlen(text);
+ let last_pos = 0;
+ let pos = 0;
+ while (pos < t_len) {
+ let sub = text + pos;
+ if (regexec(self.preg, sub, 0, 0, 0) == 0) {
+ if (pos > last_pos) {
+ let before = text + last_pos;
+ let part_len = pos - last_pos;
+ let v = Vec<char>::new();
+ for (let i = 0; i < part_len; i = i + 1) {
+ v.push(before[i]);
+ }
+ v.push(0);
+ parts.push(String { vec: v });
+ }
+ last_pos = pos + 1;
+ pos = pos + 1;
+ } else {
+ pos = pos + 1;
+ }
+ }
+ if (last_pos < t_len) {
+ parts.push(String::from(text + last_pos));
+ }
+ return parts;
+ }
+
+ fn pattern(self) -> char* {
+ return self.pattern;
+ }
+
+ fn flags(self) -> int {
+ return self.flags;
+ }
+
+ fn is_valid_pattern(pattern: char*) -> bool {
+ let test_regex = Regex::compile(pattern);
+ let valid = test_regex.is_valid();
+ test_regex.destroy();
+ return valid;
+ }
+
+ fn destroy(self) {
+ if (self.preg != 0) {
+ regfree(self.preg);
+ free(self.preg);
+ }
+ }
+}
+
+fn regex_match(pattern: char*, text: char*) -> bool {
+ let re = Regex::compile(pattern);
+ let result = re.match(text);
+ re.destroy();
+ return result;
+}
+
+fn regex_find(pattern: char*, text: char*) -> Option<Match> {
+ let re = Regex::compile(pattern);
+ let result = re.find(text);
+ re.destroy();
+ return result;
+}
+
+fn regex_count(pattern: char*, text: char*) -> int {
+ let re = Regex::compile(pattern);
+ let count = re.count(text);
+ re.destroy();
+ return count;
+}
+
+fn regex_split(pattern: char*, text: char*) -> Vec<String> {
+ let re = Regex::compile(pattern);
+ let parts = re.split(text);
+ re.destroy();
+ return parts;
+}
diff --git a/std/slice.zc b/std/slice.zc
index 7ace396..c757fbd 100644
--- a/std/slice.zc
+++ b/std/slice.zc
@@ -28,8 +28,13 @@ impl SliceIter<T> {
}
impl Slice<T> {
- fn from_array(arr: T*, len: usize) -> Slice<T> {
- return Slice<T> { data: arr, len: len };
+ fn from_array(ptr: T*, len: usize) -> Slice<T> {
+ return Slice<T> { data: ptr, len: len };
+ }
+
+ // Alias for backwards compatibility with std/mem.zc
+ fn new(data: T*, len: usize) -> Slice<T> {
+ return Slice<T> { data: data, len: len };
}
fn iterator(self) -> SliceIter<T> {
diff --git a/tests/memory/test_memory_safety.zc b/tests/memory/test_memory_safety.zc
index a5cc960..b672cc9 100644
--- a/tests/memory/test_memory_safety.zc
+++ b/tests/memory/test_memory_safety.zc
@@ -1,5 +1,6 @@
import "std/mem.zc"
+import "std/slice.zc"
// ** Globals **
let DROP_COUNT = 0;
@@ -127,11 +128,13 @@ test "test_slice" {
let data: int[5] = [1, 2, 3, 4, 5];
let s = Slice<int>::new(&data[0], 5);
f" Slice len: {(int)s.len}";
- let v2 = s.get(2);
+ let opt_v2 = s.get(2);
+ let v2 = opt_v2.unwrap();
f" Slice[2]: {v2}";
assert(v2 == 3, "Slice get failed");
- s.set(0, 99);
- let v0 = s.get(0);
+ s.data[0] = 99;
+ let opt_v0 = s.get(0);
+ let v0 = opt_v0.unwrap();
f" After set: Slice[0] = {v0}";
assert(v0 == 99, "Slice set failed");
" ✓ Slice works!";
diff --git a/tests/std/test_regex.zc b/tests/std/test_regex.zc
new file mode 100644
index 0000000..4fe176c
--- /dev/null
+++ b/tests/std/test_regex.zc
@@ -0,0 +1,187 @@
+import "std/regex.zc"
+
+fn test_basic_matching() {
+ "testing: basic matching";
+ let re = Regex::compile("abc");
+
+ if (re.match("abc")) { "literal match works"; } else { "FAILED: literal match"; }
+ if (re.match("abcdef")) { "substring match works"; } else { "FAILED: substring match"; }
+ if (!re.match("xyz")) { "not matching correctly returns false"; } else { "FAILED: mismatching"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_anchors() {
+ "testing: anchors";
+ let re = Regex::compile("^start");
+
+ if (re.match("start here")) { " ^ anchor works for start"; } else { "FAILED: ^ anchor start"; }
+ if (!re.match("no start")) { " ^ anchor rejects non-start"; } else { "FAILED: ^ anchor reject"; }
+
+ re.destroy();
+
+ let re2 = Regex::compile("end$");
+ if (re2.match("the end")) { " $ anchor works for end"; } else { "FAILED: $ anchor end"; }
+ if (!re2.match("end here")) { " $ anchor rejects non-end"; } else { "FAILED: $ anchor reject"; }
+
+ re2.destroy();
+ "";
+}
+
+fn test_wildcards() {
+ "testing: wild cards";
+ let re = Regex::compile("a.c");
+
+ if (re.match("abc")) { " . matches single char"; } else { "FAILED: . match 1"; }
+ if (re.match("axc")) { " . matches different char"; } else { "FAILED: . match 2"; }
+ if (!re.match("ac")) { " . requires exactly one char"; } else { "FAILED: . match 3"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_quantifiers() {
+ "testing: quantifiers";
+ let re1 = Regex::compile("a*b");
+ if (re1.match("b")) { " * matches zero occurrences"; } else { "FAILED: * 0"; }
+ if (re1.match("ab")) { " * matches one occurrence"; } else { "FAILED: * 1"; }
+ if (re1.match("aaab")) { " * matches multiple occurrences"; } else { "FAILED: * many"; }
+ re1.destroy();
+
+ let re2 = Regex::compile("a+b");
+ if (!re2.match("b")) { " + requires at least one"; } else { "FAILED: + 0"; }
+ if (re2.match("ab")) { " + matches one occurrence"; } else { "FAILED: + 1"; }
+ if (re2.match("aaab")) { " + matches multiple occurrences"; } else { "FAILED: + many"; }
+ re2.destroy();
+
+ let re3 = Regex::compile("colou?r");
+ if (re3.match("color")) { " ? matches with char"; } else { "FAILED: ? with"; }
+ if (re3.match("colour")) { " ? matches without char"; } else { "FAILED: ? without"; }
+ re3.destroy();
+ "";
+}
+
+fn test_character_classes() {
+ "testing: character class stuff"
+ let re = Regex::compile("[0-9]+");
+
+ if (re.match("123")) { " [0-9] matches digits"; } else { "FAILED: [0-9] match"; }
+ if (re.match("abc123")) { " [0-9] finds digits in string"; } else { "FAILED: [0-9] find"; }
+ if (!re.match("abc")) { " [0-9] rejects non-digits"; } else { "FAILED: [0-9] reject"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_alternation() {
+ "test: alternation";
+ let re = Regex::compile("cat|dog");
+
+ if (re.match("cat")) { " | matches first alternative"; } else { "FAILED: | match 1"; }
+ if (re.match("dog")) { " | matches second alternative"; } else { "FAILED: | match 2"; }
+ if (!re.match("bird")) { " | rejects non-matching"; } else { "FAILED: | reject"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_word_boundaries() {
+ "testing: word matching";
+ let re = Regex::compile("[a-zA-Z]+");
+
+ if (re.match("hello")) { " letter class matches words"; } else { "FAILED: letter match"; }
+ if (re.match("hello123")) { " letter class finds word part"; } else { "FAILED: letter part"; }
+ if (!re.match("123")) { " letter class rejects non-letters"; } else { "FAILED: letter reject"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_is_valid() {
+ "testing: patern validation"
+
+ if (Regex::is_valid_pattern("^[a-z]+$")) { " valid pattern accepted"; } else { "FAILED: pattern validation 1"; }
+ if (Regex::is_valid_pattern("(hello|world)")) { " complex pattern accepted"; } else { "FAILED: pattern validation 2"; }
+
+ "";
+}
+
+fn test_find() {
+ "testing: find functionality";
+ let re = Regex::compile("[0-9]+");
+ let m = re.find("abc123def456");
+
+ if (m.is_some()) { " find locates match"; } else { "FAILED: find match"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_count() {
+ "testing: count";
+ let re = Regex::compile("[0-9]+");
+ let count = re.count("123 456 789");
+
+ if (count >= 1) { " count finds matches"; } else { "FAILED: count matches"; }
+
+ re.destroy();
+ "";
+}
+
+fn test_convenience_functions() {
+ "testing: just some other functions and stuff";
+
+ if (regex_match("^test", "testing")) { " regex_match works"; } else { "FAILED: regex_match"; }
+ if (regex_count("a", "banana") >= 1) { " regex_count works"; } else { "FAILED: regex_count"; }
+
+ let m = regex_find("[0-9]+", "id: 42");
+ if (m.is_some()) { " regex_find works"; } else { "FAILED: regex_find"; }
+
+ "";
+}
+
+fn test_email_pattern() {
+ "test: email pattern stuff"
+ let email_re = Regex::compile("^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z][a-zA-Z]+$");
+
+ if (email_re.match("swag@swag.com")) { " valid email accepted"; } else { "FAILED: valid email"; }
+ if (email_re.match("swag.swag@swag.swag.swag")) { " complex email accepted"; } else { "FAILED: complex email"; }
+ if (!email_re.match("invalid.email")) { " invalid email rejected"; } else { "FAILED: invalid email reject"; }
+
+ email_re.destroy();
+ "";
+}
+
+fn test_url_pattern() {
+ "testing: url pattern stuff"
+ let url_re = Regex::compile("https?://[a-zA-Z0-9.-]+");
+
+ if (url_re.match("http://example.com")) { " http url matched matched"; } else { "FAILED: http url"; }
+ if (url_re.match("https://secure.example.com")) { " https url matched"; } else { "FAILED: https url"; }
+ if (!url_re.match("ftp://something.com")) { " ftp url rejected"; } else { "FAILED: ftp url reject"; }
+
+ url_re.destroy();
+ "";
+}
+
+fn main() {
+ "testing....";
+
+ test_basic_matching();
+ test_anchors();
+ test_wildcards();
+ test_quantifiers();
+ test_character_classes();
+ test_alternation();
+ test_word_boundaries();
+ test_is_valid();
+ test_find();
+ test_count();
+ test_convenience_functions();
+ test_email_pattern();
+ test_url_pattern();
+
+ "all tests worked... (hopefully.. look around for \"FAILED\" messages)";
+ "";
+}