diff options
| author | czjstmax <maxwasmailed@proton.me> | 2026-01-31 20:09:51 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 20:09:51 +0100 |
| commit | 5f283b75488e89d2c4f261ae83e0424daec29554 (patch) | |
| tree | ba1637d3885213095b312f81a477c33b1ebca6aa | |
| parent | d2e2617dec584884b92eb452f377b20c0bf8f321 (diff) | |
| parent | 13af49ba93d653fb6306604889c4ef66e9018873 (diff) | |
Merge branch 'z-libs:main' into main
| -rw-r--r-- | docs/std/slice.md | 15 | ||||
| -rw-r--r-- | src/codegen/codegen.c | 214 | ||||
| -rw-r--r-- | src/codegen/codegen_decl.c | 1 | ||||
| -rw-r--r-- | src/codegen/codegen_main.c | 33 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 10 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 114 | ||||
| -rw-r--r-- | src/parser/parser_struct.c | 115 | ||||
| -rw-r--r-- | std.zc | 1 | ||||
| -rw-r--r-- | std/mem.zc | 23 | ||||
| -rw-r--r-- | std/regex.zc | 198 | ||||
| -rw-r--r-- | std/slice.zc | 9 | ||||
| -rw-r--r-- | tests/memory/test_memory_safety.zc | 9 | ||||
| -rw-r--r-- | tests/std/test_regex.zc | 187 |
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 @@ -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" @@ -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)"; + ""; +} |
