From b8834bc878f47b2cee02588607beeac1d1ae1246 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Tue, 27 Jan 2026 23:25:25 +0000 Subject: Added script for checking examples with 'make test' too --- tests/run_example_transpile.sh | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 tests/run_example_transpile.sh (limited to 'tests') diff --git a/tests/run_example_transpile.sh b/tests/run_example_transpile.sh new file mode 100755 index 0000000..c08a3ea --- /dev/null +++ b/tests/run_example_transpile.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +ZC="./zc" +EXAMPLES_DIR="examples" +FAIL_COUNT=0 +PASS_COUNT=0 + +echo "Running Example Transpilation Tests..." + +while IFS= read -r file; do + echo -n "Transpiling $file... " + + OUTPUT=$($ZC transpile "$file" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -eq 0 ]; then + echo "PASS" + PASS_COUNT=$((PASS_COUNT + 1)) + [ -f "out.c" ] && rm "out.c" + [ -f "a.out" ] && rm "a.out" + else + echo "FAIL" + echo "$OUTPUT" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + +done < <(find "$EXAMPLES_DIR" -name "*.zc") + +echo "----------------------------------------" +echo "Summary:" +echo "-> Passed: $PASS_COUNT" +echo "-> Failed: $FAIL_COUNT" +echo "----------------------------------------" + +if [ $FAIL_COUNT -ne 0 ]; then + exit 1 +fi + +exit 0 -- cgit v1.2.3 From 6a28507b59c4a5b89562962817b45e517f442cd4 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Tue, 27 Jan 2026 23:46:47 +0000 Subject: Fix for #143 --- src/codegen/codegen.c | 8 ++++++++ tests/memory/test_unsafe.zc | 15 +++++++++++++++ 2 files changed, 23 insertions(+) (limited to 'tests') diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 7c58943..b090546 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -745,7 +745,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; diff --git a/tests/memory/test_unsafe.zc b/tests/memory/test_unsafe.zc index fe1150f..6114d6c 100644 --- a/tests/memory/test_unsafe.zc +++ b/tests/memory/test_unsafe.zc @@ -54,3 +54,18 @@ test "test_static_local" { assert b == 2; assert c == 3; } + +struct CastFoo { + val: int; +} + +fn test_cast_precedence_helper(ptr: void*) -> int { + return ((CastFoo*)ptr)->val; +} + +test "test_cast_precedence" { + let f = CastFoo{val: 42}; + let ptr = (void*)&f; + let val = test_cast_precedence_helper(ptr); + assert(val == 42, "Cast precedence failed"); +} -- cgit v1.2.3 From da12345b6e3c297e530764c4424b7d9e6a1cb65e Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 01:19:20 +0000 Subject: Fix for #141 --- src/codegen/codegen_stmt.c | 11 +++++----- src/lexer/token.c | 8 +++++++ src/parser/parser_core.c | 2 +- src/parser/parser_expr.c | 37 +++++++++++++++++++++++++++++++-- src/parser/parser_stmt.c | 6 +++--- tests/features/test_implicit_fstring.zc | 19 +++++++++++++++++ tests/features/test_traits_suite.zc | 2 +- 7 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 tests/features/test_implicit_fstring.zc (limited to 'tests') diff --git a/src/codegen/codegen_stmt.c b/src/codegen/codegen_stmt.c index 70fb297..2f9a2ba 100644 --- a/src/codegen/codegen_stmt.c +++ b/src/codegen/codegen_stmt.c @@ -884,8 +884,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[0], id, - check); + fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[0], id, check); } } else @@ -903,8 +902,8 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[i], - id, field); + fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[i], id, + field); } } else @@ -916,8 +915,8 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " ZC_AUTO %s = _tmp_%d.v%d;\n", node->destruct.names[i], - id, i); + fprintf(out, " ZC_AUTO %s = _tmp_%d.v%d;\n", node->destruct.names[i], id, + i); } } } diff --git a/src/lexer/token.c b/src/lexer/token.c index decabbe..6696a5c 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -149,6 +149,14 @@ Token lexer_next(Lexer *l) { return (Token){TOK_DEF, s, 3, start_line, start_col}; } + if (len == 5 && strncmp(s, "trait", 5) == 0) + { + return (Token){TOK_TRAIT, s, 5, start_line, start_col}; + } + if (len == 4 && strncmp(s, "impl", 4) == 0) + { + return (Token){TOK_IMPL, s, 4, start_line, start_col}; + } if (len == 8 && strncmp(s, "autofree", 8) == 0) { return (Token){TOK_AUTOFREE, s, 8, start_line, start_col}; diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index e9a418e..ba6f2fe 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -700,7 +700,7 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * { // Simplistic Debug for now, I know. code = xmalloc(1024); - sprintf(code, "impl %s { fn to_string(self) -> char* { return \"%s { ... }\"; } }", + sprintf(code, "impl %s { fn to_string(self) -> char* { return \"%s {{ ... }}\"; } }", name, name); } else if (0 == strcmp(trait, "Copy")) diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index df2dcf6..332d9c3 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -935,6 +935,17 @@ static ASTNode *create_fstring_block(ParserContext *ctx, const char *content) free(txt); } + // Handle escape {{ + if (brace[1] == '{') + { + ASTNode *cat = ast_create(NODE_RAW_STMT); + cat->raw_stmt.content = xstrdup("strcat(_b, \"{\");"); + tail->next = cat; + tail = cat; + cur = brace + 2; + continue; + } + char *end_brace = strchr(brace, '}'); if (!end_brace) { @@ -1100,8 +1111,30 @@ static ASTNode *parse_float_literal(Token t) } // Parse string literal -static ASTNode *parse_string_literal(Token t) +static ASTNode *parse_string_literal(ParserContext *ctx, Token t) { + // Check for implicit interpolation + int has_interpolation = 0; + for (int i = 1; i < t.len - 1; i++) + { + if (t.start[i] == '{') + { + has_interpolation = 1; + break; + } + } + + if (has_interpolation) + { + + char *inner = xmalloc(t.len); + strncpy(inner, t.start + 1, t.len - 2); + inner[t.len - 2] = 0; + ASTNode *node = create_fstring_block(ctx, inner); + free(inner); + return node; + } + ASTNode *node = ast_create(NODE_EXPR_LITERAL); node->literal.type_kind = LITERAL_STRING; node->literal.string_val = xmalloc(t.len); @@ -1276,7 +1309,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) } else if (t.type == TOK_STRING) { - node = parse_string_literal(t); + node = parse_string_literal(ctx, t); } else if (t.type == TOK_FSTRING) { diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index ab5b89c..85e9825 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -1877,7 +1877,7 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) lexer_next(&lookahead); TokenType next_type = lexer_peek(&lookahead).type; - if (next_type == TOK_SEMICOLON || next_type == TOK_DOTDOT) + if (next_type == TOK_SEMICOLON || next_type == TOK_DOTDOT || next_type == TOK_RBRACE) { Token t = lexer_next(l); // consume string @@ -1894,8 +1894,7 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) inner[t.len - 2] = 0; } - // ; means println (end of line), .. means print (continuation) - int is_ln = (next_type == TOK_SEMICOLON); + int is_ln = (next_type == TOK_SEMICOLON || next_type == TOK_RBRACE); char **used_syms = NULL; int used_count = 0; char *code = @@ -1913,6 +1912,7 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) lexer_next(l); // consume optional ; } } + // If TOK_RBRACE, do not consume it, so parse_block can see it and terminate loop. ASTNode *n = ast_create(NODE_RAW_STMT); // Append semicolon to Statement Expression to make it a valid statement diff --git a/tests/features/test_implicit_fstring.zc b/tests/features/test_implicit_fstring.zc new file mode 100644 index 0000000..85a6c86 --- /dev/null +++ b/tests/features/test_implicit_fstring.zc @@ -0,0 +1,19 @@ + +test "implicit_fstring_interpolation" { + let result = 123; + let s = "{result}"; + // Should be "123" + assert(strcmp(s, "123") == 0, "Implicit f-string failed"); +} + +test "implicit_fstring_complex" { + let a = 10; + let b = 20; + let s = "Sum: {a + b}"; + assert(strcmp(s, "Sum: 30") == 0, "Complex implicit f-string failed"); +} + +test "no_interpolation" { + let s = "Hello World"; + assert(strcmp(s, "Hello World") == 0, "Plain string failed"); +} diff --git a/tests/features/test_traits_suite.zc b/tests/features/test_traits_suite.zc index 205bdf6..2ff8378 100644 --- a/tests/features/test_traits_suite.zc +++ b/tests/features/test_traits_suite.zc @@ -90,7 +90,7 @@ test "test_derive" { // Debug let s = p1.to_string(); - assert(strcmp(s, "Point { ... }") == 0, "Debug string matches"); + assert(strcmp(s, "Point {{ ... }}") == 0, "Debug string matches"); // Clone let p2 = p1.clone(); -- cgit v1.2.3 From 985072a63b72332b1a012a5d686771da93342349 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 10:52:36 +0000 Subject: fix: prompt segfault and strict string eq --- src/codegen/codegen.c | 26 ++++++++++++++++---------- tests/std/test_env.zc | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) (limited to 'tests') diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index b090546..a66f179 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -240,6 +240,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) 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) @@ -252,16 +253,8 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) is_null_compare = 1; } - if (is_null_compare) - { - // Direct pointer comparison for NULL - 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 + if (!is_null_compare && strcmp(t1, "string") == 0 && t2 && + strcmp(t2, "string") == 0) { fprintf(out, "(strcmp("); codegen_expression(ctx, node->binary.left, out); @@ -276,6 +269,19 @@ 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 { diff --git a/tests/std/test_env.zc b/tests/std/test_env.zc index 25d5bc1..4b68712 100644 --- a/tests/std/test_env.zc +++ b/tests/std/test_env.zc @@ -30,7 +30,7 @@ test "test_std_env_get_dup" { assert(env_var.is_some(), "env_var should have a value"); let value = env_var.unwrap(); - assert(value.c_str() == "ok3", "value should be ok3"); + assert(strcmp(value.c_str(), "ok3") == 0, "value should be ok3"); value.free(); -- cgit v1.2.3 From fd692ab7bb9f7b1e8f5d878a16154a4a03d0f6f9 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 19:52:37 +0000 Subject: Opaque structs and aliases + some improvements --- README.md | 72 ++++++++++++++++++- src/ast/ast.c | 17 +++++ src/ast/ast.h | 10 +++ src/lexer/token.c | 4 ++ src/lsp/lsp_analysis.c | 71 ++++++++++++++++++- src/lsp/lsp_index.c | 52 +++++++++++++- src/parser/parser.h | 26 +++---- src/parser/parser_core.c | 30 ++++++-- src/parser/parser_decl.c | 64 ++++++++++++++++- src/parser/parser_expr.c | 133 +++++++++++++++++++++++++++++++++++- src/parser/parser_struct.c | 12 ++-- src/parser/parser_type.c | 19 +++++- src/parser/parser_utils.c | 14 +++- src/zprep.h | 1 + tests/features/_opaque_alias_lib.zc | 13 ++++ tests/features/_opaque_lib.zc | 15 ++++ tests/features/test_opaque.zc | 18 +++++ tests/features/test_opaque_alias.zc | 13 ++++ 18 files changed, 549 insertions(+), 35 deletions(-) create mode 100644 tests/features/_opaque_alias_lib.zc create mode 100644 tests/features/_opaque_lib.zc create mode 100644 tests/features/test_opaque.zc create mode 100644 tests/features/test_opaque_alias.zc (limited to 'tests') diff --git a/README.md b/README.md index 9550208..a35e0de 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,11 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [Arrays](#arrays) - [Tuples](#tuples) - [Structs](#structs) + - [Opaque Structs](#opaque-structs) - [Enums](#enums) - [Unions](#unions) - [Type Aliases](#type-aliases) + - [Opaque Type Aliases](#opaque-type-aliases) - [4. Functions & Lambdas](#4-functions--lambdas) - [Functions](#functions) - [Const Arguments](#const-arguments) @@ -91,6 +93,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [Volatile](#volatile) - [Named Constraints](#named-constraints) - [15. Build Directives](#15-build-directives) + - [16. Keywords](#16-keywords) - [Standard Library](#standard-library) - [Tooling](#tooling) - [Language Server (LSP)](#language-server-lsp) @@ -261,6 +264,29 @@ struct Flags { > **Note**: Structs use [Move Semantics](#move-semantics--copy-safety) by default. Fields can be accessed via `.` even on pointers (Auto-Dereference). +#### Opaque Structs +You can define a struct as `opaque` to restrict access to its fields to the defining module only, while still allowing the struct to be allocated on the stack (size is known). + +```zc +// In user.zc +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // OK: Inside module +} + +// In main.zc +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // Error: Cannot access private field 'id' +} +``` + #### Enums Tagged unions (Sum types) capable of holding data. ```zc @@ -287,6 +313,27 @@ alias ID = int; alias PointMap = Map; ``` +#### Opaque Type Aliases +You can define a type alias as `opaque` to create a new type that is distinct from its underlying type outside of the defining module. This provides strong encapsulation and type safety without the runtime overhead of a wrapper struct. + +```zc +// In library.zc +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // Implicit conversion allowed inside module +} + +// In main.zc +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // Error: Type validation failed + // let h2: Handle = 10; // Error: Type validation failed +} +``` + ### 4. Functions & Lambdas #### Functions @@ -980,6 +1027,29 @@ import "raylib.h" fn main() { ... } ``` +### 16. Keywords + +The following keywords are reserved in Zen C. + +#### Declarations +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### Control Flow +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### Special +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### Constants +`true`, `false`, `null` + +#### C Reserved +The following identifiers are reserved because they are keywords in C11: +`auto`, `case`, `char`, `default`, `do`, `double`, `extern`, `float`, `inline`, `int`, `long`, `register`, `restrict`, `short`, `signed`, `switch`, `typedef`, `unsigned`, `void`, `_Atomic`, `_Bool`, `_Complex`, `_Generic`, `_Imaginary`, `_Noreturn`, `_Static_assert`, `_Thread_local` + +#### Operators +`and`, `or` + --- ## Standard Library @@ -1190,7 +1260,7 @@ fn add_kernel(a: float*, b: float*, c: float*, n: int) { } fn main() { - const N = 1024; + def N = 1024; let d_a = cuda_alloc(N); let d_b = cuda_alloc(N); let d_c = cuda_alloc(N); diff --git a/src/ast/ast.c b/src/ast/ast.c index 0799845..f4922a6 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -168,6 +168,18 @@ int type_eq(Type *a, Type *b) { return 0 == strcmp(a->name, b->name); } + if (a->kind == TYPE_ALIAS) + { + if (a->alias.is_opaque_alias) + { + if (b->kind != TYPE_ALIAS || !b->alias.is_opaque_alias) + { + return 0; + } + return 0 == strcmp(a->name, b->name); + } + return type_eq(a->inner, b); + } if (a->kind == TYPE_POINTER || a->kind == TYPE_ARRAY) { return type_eq(a->inner, b->inner); @@ -340,6 +352,8 @@ static char *type_to_string_impl(Type *t) } return xstrdup(t->name); } + case TYPE_ALIAS: + return xstrdup(t->name); default: return xstrdup("unknown"); @@ -524,6 +538,9 @@ static char *type_to_c_string_impl(Type *t) case TYPE_GENERIC: return xstrdup(t->name); + case TYPE_ALIAS: + return type_to_c_string(t->inner); + case TYPE_ENUM: return xstrdup(t->name); diff --git a/src/ast/ast.h b/src/ast/ast.h index b272cae..a868bf0 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -58,6 +58,7 @@ typedef enum TYPE_ARRAY, ///< Fixed size array [N]. TYPE_FUNCTION, ///< Function pointer or reference. TYPE_GENERIC, ///< Generic type parameter (T). + TYPE_ALIAS, ///< Opaque type alias. TYPE_UNKNOWN ///< Unknown/unresolved type. } TypeKind; @@ -84,6 +85,11 @@ typedef struct Type int has_drop; ///< 1 if type implements Drop trait (RAII). int has_iterable; ///< 1 if type implements Iterable trait. } traits; + struct + { + int is_opaque_alias; + char *alias_defined_in_file; + } alias; }; } Type; @@ -263,6 +269,8 @@ struct ASTNode { char *alias; char *original_type; + int is_opaque; + char *defined_in_file; } type_alias; struct @@ -436,6 +444,8 @@ struct ASTNode Attribute *attributes; // Custom attributes char **used_structs; // Names of structs used/mixed-in int used_struct_count; + int is_opaque; + char *defined_in_file; // File where the struct is defined (for privacy check) } strct; struct diff --git a/src/lexer/token.c b/src/lexer/token.c index 6696a5c..095815d 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -201,6 +201,10 @@ Token lexer_next(Lexer *l) { return (Token){TOK_OR, s, 2, start_line, start_col}; } + if (len == 6 && strncmp(s, "opaque", 6) == 0) + { + return (Token){TOK_OPAQUE, s, 6, start_line, start_col}; + } // F-Strings if (len == 1 && s[0] == 'f' && s[1] == '"') diff --git a/src/lsp/lsp_analysis.c b/src/lsp/lsp_analysis.c index 088bede..0367d93 100644 --- a/src/lsp/lsp_analysis.c +++ b/src/lsp/lsp_analysis.c @@ -454,11 +454,80 @@ void lsp_completion(const char *uri, int line, int col, int id) cJSON_AddStringToObject(item, "label", s->name); cJSON_AddNumberToObject(item, "kind", 22); // Struct char detail[256]; - sprintf(detail, "struct %s", s->name); + sprintf(detail, "%sstruct %s", + (s->node && s->node->type == NODE_STRUCT && s->node->strct.is_opaque) + ? "opaque " + : "", + s->name); cJSON_AddStringToObject(item, "detail", detail); cJSON_AddItemToArray(items, item); s = s->next; } + + // Globals and Constants + StructRef *g = g_project->ctx->parsed_globals_list; + while (g) + { + if (g->node) + { + cJSON *item = cJSON_CreateObject(); + char *name = + (g->node->type == NODE_CONST) ? g->node->var_decl.name : g->node->var_decl.name; + cJSON_AddStringToObject(item, "label", name); + cJSON_AddNumberToObject(item, "kind", 21); // Constant/Variable + char detail[256]; + sprintf(detail, "%s %s", (g->node->type == NODE_CONST) ? "const" : "var", name); + cJSON_AddStringToObject(item, "detail", detail); + cJSON_AddItemToArray(items, item); + } + g = g->next; + } + + // Enums + StructRef *e = g_project->ctx->parsed_enums_list; + while (e) + { + if (e->node) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "label", e->node->enm.name); + cJSON_AddNumberToObject(item, "kind", 13); // Enum + char detail[256]; + sprintf(detail, "enum %s", e->node->enm.name); + cJSON_AddStringToObject(item, "detail", detail); + cJSON_AddItemToArray(items, item); + } + e = e->next; + } + + // Type Aliases + TypeAlias *ta = g_project->ctx->type_aliases; + while (ta) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "label", ta->alias); + cJSON_AddNumberToObject(item, "kind", 8); // Interface/Reference + char detail[256]; + sprintf(detail, "alias %s = %s", ta->alias, ta->original_type); + cJSON_AddStringToObject(item, "detail", detail); + cJSON_AddItemToArray(items, item); + ta = ta->next; + } + + // Keywords + const char *keywords[] = { + "fn", "struct", "enum", "alias", "return", "if", "else", "for", "while", + "break", "continue", "true", "false", "int", "char", "bool", "string", "void", + "import", "module", "test", "assert", "defer", "sizeof", "opaque", "unsafe", "asm", + "trait", "impl", "u8", "u16", "u32", "u64", "i8", "i16", "i32", + "i64", "f32", "f64", "usize", "isize", "const", "var", NULL}; + for (int i = 0; keywords[i]; i++) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "label", keywords[i]); + cJSON_AddNumberToObject(item, "kind", 14); // Keyword + cJSON_AddItemToArray(items, item); + } } cJSON_AddItemToObject(root, "result", items); diff --git a/src/lsp/lsp_index.c b/src/lsp/lsp_index.c index 285dec3..975e153 100644 --- a/src/lsp/lsp_index.c +++ b/src/lsp/lsp_index.c @@ -151,7 +151,32 @@ void lsp_walk_node(LSPIndex *idx, ASTNode *node) else if (node->type == NODE_STRUCT) { char hover[256]; - sprintf(hover, "struct %s", node->strct.name); + if (node->strct.is_opaque) + { + sprintf(hover, "opaque struct %s", node->strct.name); + } + else + { + sprintf(hover, "struct %s", node->strct.name); + } + lsp_index_add_def(idx, node->token, hover, node); + } + else if (node->type == NODE_ENUM) + { + char hover[256]; + sprintf(hover, "enum %s", node->enm.name); + lsp_index_add_def(idx, node->token, hover, node); + } + else if (node->type == NODE_TYPE_ALIAS) + { + char hover[256]; + sprintf(hover, "alias %s = %s", node->type_alias.alias, node->type_alias.original_type); + lsp_index_add_def(idx, node->token, hover, node); + } + else if (node->type == NODE_TRAIT) + { + char hover[256]; + sprintf(hover, "trait %s", node->trait.name); lsp_index_add_def(idx, node->token, hover, node); } @@ -196,6 +221,31 @@ void lsp_walk_node(LSPIndex *idx, ASTNode *node) lsp_walk_node(idx, node->call.callee); lsp_walk_node(idx, node->call.args); break; + case NODE_MATCH: + lsp_walk_node(idx, node->match_stmt.expr); + lsp_walk_node(idx, node->match_stmt.cases); + break; + case NODE_MATCH_CASE: + lsp_walk_node(idx, node->match_case.guard); + lsp_walk_node(idx, node->match_case.body); + break; + case NODE_FOR: + lsp_walk_node(idx, node->for_stmt.init); + lsp_walk_node(idx, node->for_stmt.condition); + lsp_walk_node(idx, node->for_stmt.step); + lsp_walk_node(idx, node->for_stmt.body); + break; + case NODE_FOR_RANGE: + lsp_walk_node(idx, node->for_range.start); + lsp_walk_node(idx, node->for_range.end); + lsp_walk_node(idx, node->for_range.body); + break; + case NODE_LOOP: + lsp_walk_node(idx, node->loop_stmt.body); + break; + case NODE_DEFER: + lsp_walk_node(idx, node->defer_stmt.stmt); + break; default: break; } diff --git a/src/parser/parser.h b/src/parser/parser.h index cf57971..262c359 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -261,6 +261,8 @@ typedef struct TypeAlias char *alias; ///< New type name. char *original_type; ///< Original type. struct TypeAlias *next; + int is_opaque; + char *defined_in_file; } TypeAlias; /** @@ -514,12 +516,9 @@ char *sanitize_mangled_name(const char *name); /** * @brief Registers a type alias. */ -void register_type_alias(ParserContext *ctx, const char *alias, const char *original); - -/** - * @brief Finds a type alias. - */ -const char *find_type_alias(ParserContext *ctx, const char *alias); +TypeAlias *find_type_alias_node(ParserContext *ctx, const char *name); +void register_type_alias(ParserContext *ctx, const char *alias, const char *original, int is_opaque, + const char *defined_in_file); /** * @brief Registers an implementation. @@ -681,10 +680,6 @@ void register_selective_import(ParserContext *ctx, const char *symbol, const cha SelectiveImport *find_selective_import(ParserContext *ctx, const char *name); // Type Aliases -/** - * @brief Registers a type alias. - */ -void register_type_alias(ParserContext *ctx, const char *alias, const char *original); /** * @brief Finds a type alias. @@ -740,6 +735,11 @@ FuncSig *find_func(ParserContext *ctx, const char *name); */ Type *parse_type_formal(ParserContext *ctx, Lexer *l); +/** + * @brief Checks compatibility of opaque aliases (allows access within defining file). + */ +int check_opaque_alias_compat(ParserContext *ctx, Type *a, Type *b); + /** * @brief Parses a type. */ @@ -889,7 +889,7 @@ ASTNode *parse_def(ParserContext *ctx, Lexer *l); /** * @brief Parses a type alias. */ -ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l); +ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l, int is_opaque); /** * @brief Parses a function definition. @@ -899,7 +899,7 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async); /** * @brief Parses a struct definition. */ -ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union); +ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union, int is_opaque); /** * @brief Parses an enum definition. @@ -963,4 +963,4 @@ char *patch_self_args(const char *args, const char *struct_name); */ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l); -#endif // PARSER_H +#endif // PARSER_H \ No newline at end of file diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index ba6f2fe..43137b1 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -338,7 +338,7 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) } else if (0 == strncmp(t.start, "struct", 6) && 6 == t.len) { - s = parse_struct(ctx, l, 0); + s = parse_struct(ctx, l, 0, 0); if (s && s->type == NODE_STRUCT) { s->strct.is_packed = attr_packed; @@ -436,7 +436,7 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) } else if (0 == strncmp(t.start, "type", 4) && 4 == t.len) { - s = parse_type_alias(ctx, l); + s = parse_type_alias(ctx, l, 0); } else if (0 == strncmp(t.start, "raw", 3) && 3 == t.len) { @@ -482,9 +482,31 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) lexer_next(l); } } + else if (t.type == TOK_OPAQUE) + { + lexer_next(l); // eat opaque + Token next = lexer_peek(l); + if (0 == strncmp(next.start, "struct", 6) && 6 == next.len) + { + s = parse_struct(ctx, l, 0, 1); + if (s && s->type == NODE_STRUCT) + { + s->strct.is_packed = attr_packed; + s->strct.align = attr_align; + } + } + else if (next.type == TOK_ALIAS) + { + s = parse_type_alias(ctx, l, 1); + } + else + { + zpanic_at(next, "Expected 'struct' or 'alias' after 'opaque'"); + } + } else if (t.type == TOK_ALIAS) { - s = parse_type_alias(ctx, l); + s = parse_type_alias(ctx, l, 0); } else if (t.type == TOK_ASYNC) { @@ -506,7 +528,7 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) else if (t.type == TOK_UNION) { - s = parse_struct(ctx, l, 1); + s = parse_struct(ctx, l, 1, 0); } else if (t.type == TOK_TRAIT) { diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c index 98f46e1..c96ca36 100644 --- a/src/parser/parser_decl.c +++ b/src/parser/parser_decl.c @@ -579,6 +579,10 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) { type_obj->inner = init->type_info->inner; // Shallow copy for inner } + if (init->type_info->kind == TYPE_ALIAS) + { + type_obj->alias = init->type_info->alias; + } // Copy function type args for lambda/closure support if (init->type_info->args && init->type_info->arg_count > 0) { @@ -631,6 +635,59 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) // Register in symbol table with actual token add_symbol_with_token(ctx, name, type, type_obj, name_tok); + if (init && type_obj) + { + Type *t = init->type_info; + if (!t && init->type == NODE_EXPR_VAR) + { + t = find_symbol_type_info(ctx, init->var_ref.name); + } + + // Literal type construction for validation + Type *temp_literal_type = NULL; + if (!t && init->type == NODE_EXPR_LITERAL) + { + if (init->literal.type_kind == LITERAL_INT) + { + temp_literal_type = type_new(TYPE_INT); + } + else if (init->literal.type_kind == LITERAL_FLOAT) + { + temp_literal_type = type_new(TYPE_FLOAT); + } + else if (init->literal.type_kind == LITERAL_STRING) + { + temp_literal_type = type_new(TYPE_STRING); + } + else if (init->literal.type_kind == LITERAL_CHAR) + { + temp_literal_type = type_new(TYPE_CHAR); + } + t = temp_literal_type; + } + + // Special case for literals: if implicit conversion works + if (t && !type_eq(type_obj, t)) + { + // Allow integer compatibility if types are roughly ints (lax check in type_eq handles + // most, but let's be safe) + if (!check_opaque_alias_compat(ctx, type_obj, t)) + { + char *expected = type_to_string(type_obj); + char *got = type_to_string(t); + zpanic_at(init->token, "Type validation failed. Expected '%s', but got '%s'", + expected, got); + free(expected); + free(got); + } + } + + if (temp_literal_type) + { + free(temp_literal_type); // Simple free, shallow + } + } + // NEW: Capture Const Integer Values if (init && init->type == NODE_EXPR_LITERAL && init->literal.type_kind == LITERAL_INT) { @@ -839,7 +896,7 @@ ASTNode *parse_def(ParserContext *ctx, Lexer *l) return o; } -ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l) +ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l, int is_opaque) { lexer_next(l); // consume 'type' or 'alias' Token n = lexer_next(l); @@ -859,8 +916,11 @@ ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l) strncpy(node->type_alias.alias, n.start, n.len); node->type_alias.alias[n.len] = 0; node->type_alias.original_type = o; + node->type_alias.is_opaque = is_opaque; + node->type_alias.defined_in_file = g_current_filename ? xstrdup(g_current_filename) : NULL; - register_type_alias(ctx, node->type_alias.alias, o); + register_type_alias(ctx, node->type_alias.alias, o, is_opaque, + node->type_alias.defined_in_file); return node; } diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index ee1f96f..8f3579a 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -1,3 +1,43 @@ +#include "../codegen/codegen.h" + +int check_opaque_alias_compat(ParserContext *ctx, Type *a, Type *b) +{ + if (!a || !b) + { + return 0; + } + + int a_is_opaque = (a->kind == TYPE_ALIAS && a->alias.is_opaque_alias); + int b_is_opaque = (b->kind == TYPE_ALIAS && b->alias.is_opaque_alias); + + if (!a_is_opaque && !b_is_opaque) + { + return 1; + } + + if (a_is_opaque) + { + if (a->alias.alias_defined_in_file && g_current_filename && + strcmp(a->alias.alias_defined_in_file, g_current_filename) == 0) + { + return check_opaque_alias_compat(ctx, a->inner, b); + } + return 0; + } + + if (b_is_opaque) + { + if (b->alias.alias_defined_in_file && g_current_filename && + strcmp(b->alias.alias_defined_in_file, g_current_filename) == 0) + { + return check_opaque_alias_compat(ctx, a, b->inner); + } + return 0; + } + + return 0; +} + #include "../zen/zen_facts.h" #include "parser.h" #include @@ -1083,6 +1123,7 @@ static ASTNode *create_fstring_block(ParserContext *ctx, const char *content) static ASTNode *parse_int_literal(Token t) { ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_INT; node->type_info = type_new(TYPE_INT); char *s = token_strdup(t); @@ -1104,6 +1145,7 @@ static ASTNode *parse_int_literal(Token t) static ASTNode *parse_float_literal(Token t) { ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_FLOAT; node->literal.float_val = atof(t.start); node->type_info = type_new(TYPE_F64); @@ -1136,6 +1178,7 @@ static ASTNode *parse_string_literal(ParserContext *ctx, Token t) } ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_STRING; node->literal.string_val = xmalloc(t.len); strncpy(node->literal.string_val, t.start + 1, t.len - 2); @@ -1159,6 +1202,7 @@ static ASTNode *parse_fstring_literal(ParserContext *ctx, Token t) static ASTNode *parse_char_literal(Token t) { ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_CHAR; node->literal.string_val = token_strdup(t); node->type_info = type_new(TYPE_I8); @@ -2051,6 +2095,20 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) sprintf(prefixed, "%s_%s", ctx->current_module_prefix, acc); struct_name = prefixed; } + + // Opaque Struct Check + ASTNode *def = find_struct_def(ctx, struct_name); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(lexer_peek(l), + "Cannot initialize opaque struct '%s' outside its module", + struct_name); + } + } lexer_next(l); node = ast_create(NODE_EXPR_STRUCT_INIT); node->struct_init.struct_name = struct_name; @@ -4045,6 +4103,30 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) node->member.field = token_strdup(field); node->member.is_pointer_access = 1; + // Opaque Check + int is_ptr_dummy = 0; + char *alloc_name = NULL; + char *sname = + resolve_struct_name_from_type(ctx, lhs->type_info, &is_ptr_dummy, &alloc_name); + if (sname) + { + ASTNode *def = find_struct_def(ctx, sname); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(field, "Cannot access private field '%s' of opaque struct '%s'", + node->member.field, sname); + } + } + if (alloc_name) + { + free(alloc_name); + } + } + node->type_info = get_field_type(ctx, lhs->type_info, node->member.field); if (node->type_info) { @@ -4074,6 +4156,30 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) node->member.field = token_strdup(field); node->member.is_pointer_access = 2; + // Opaque Check + int is_ptr_dummy = 0; + char *alloc_name = NULL; + char *sname = + resolve_struct_name_from_type(ctx, lhs->type_info, &is_ptr_dummy, &alloc_name); + if (sname) + { + ASTNode *def = find_struct_def(ctx, sname); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(field, "Cannot access private field '%s' of opaque struct '%s'", + node->member.field, sname); + } + } + if (alloc_name) + { + free(alloc_name); + } + } + node->type_info = get_field_type(ctx, lhs->type_info, node->member.field); if (node->type_info) { @@ -4653,6 +4759,30 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) node->member.field = token_strdup(field); node->member.is_pointer_access = 0; + // Opaque Check + int is_ptr_dummy = 0; + char *alloc_name = NULL; + char *sname = + resolve_struct_name_from_type(ctx, lhs->type_info, &is_ptr_dummy, &alloc_name); + if (sname) + { + ASTNode *def = find_struct_def(ctx, sname); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(field, "Cannot access private field '%s' of opaque struct '%s'", + node->member.field, sname); + } + } + if (alloc_name) + { + free(alloc_name); + } + } + node->member.field = token_strdup(field); node->member.is_pointer_access = 0; @@ -5540,7 +5670,8 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } else { - if (type_eq(lhs->type_info, rhs->type_info)) + if (type_eq(lhs->type_info, rhs->type_info) || + check_opaque_alias_compat(ctx, lhs->type_info, rhs->type_info)) { bin->type_info = lhs->type_info; } diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index 84450ba..82dd346 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -652,7 +652,7 @@ ASTNode *parse_impl(ParserContext *ctx, Lexer *l) } } -ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) +ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union, int is_opaque) { lexer_next(l); // eat struct or union @@ -705,6 +705,7 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) n->strct.is_union = is_union; n->strct.fields = NULL; n->strct.is_incomplete = 1; + n->strct.is_opaque = is_opaque; return n; } @@ -800,10 +801,6 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) ASTNode *nf = ast_create(NODE_FIELD); nf->field.name = xstrdup(f->field.name); nf->field.type = xstrdup(f->field.type); - // Copy type info? Ideally deep copy or ref - // For now, we leave it NULL or shallow copy if needed, but mixins usually - // aren't generic params themselves in the same way. - // Let's shallow copy for safety if it exists. nf->type_info = f->type_info; if (!h) @@ -829,7 +826,6 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) free(use_name); continue; } - // --------------------------------------- if (t.type == TOK_IDENT) { @@ -904,8 +900,10 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) node->strct.generic_params = gps; node->strct.generic_param_count = gp_count; node->strct.is_union = is_union; + node->strct.is_opaque = is_opaque; node->strct.used_structs = temp_used_structs; node->strct.used_struct_count = temp_used_count; + node->strct.defined_in_file = g_current_filename ? xstrdup(g_current_filename) : NULL; if (gp_count > 0) { @@ -1102,7 +1100,7 @@ ASTNode *parse_enum(ParserContext *ctx, Lexer *l) node->enm.name = ename; node->enm.variants = h; - node->enm.generic_param = gp; // 3. Store generic param + node->enm.generic_param = gp; // Store generic param if (gp) { diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c index c01f061..65f2848 100644 --- a/src/parser/parser_type.c +++ b/src/parser/parser_type.c @@ -33,12 +33,25 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) char *name = token_strdup(t); // Check for alias - const char *aliased = find_type_alias(ctx, name); - if (aliased) + TypeAlias *alias_node = find_type_alias_node(ctx, name); + if (alias_node) { free(name); Lexer tmp; - lexer_init(&tmp, aliased); + lexer_init(&tmp, alias_node->original_type); + + if (alias_node->is_opaque) + { + Type *underlying = parse_type_formal(ctx, &tmp); + Type *wrapper = type_new(TYPE_ALIAS); + wrapper->name = xstrdup(alias_node->alias); + wrapper->inner = underlying; + wrapper->alias.is_opaque_alias = 1; + wrapper->alias.alias_defined_in_file = + alias_node->defined_in_file ? xstrdup(alias_node->defined_in_file) : NULL; + return wrapper; + } + return parse_type_formal(ctx, &tmp); } diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 4e85500..48418b6 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -402,23 +402,33 @@ void add_to_struct_list(ParserContext *ctx, ASTNode *node) ctx->parsed_structs_list = r; } -void register_type_alias(ParserContext *ctx, const char *alias, const char *original) +void register_type_alias(ParserContext *ctx, const char *alias, const char *original, int is_opaque, + const char *defined_in_file) { TypeAlias *ta = xmalloc(sizeof(TypeAlias)); ta->alias = xstrdup(alias); ta->original_type = xstrdup(original); + ta->is_opaque = is_opaque; + ta->defined_in_file = defined_in_file ? xstrdup(defined_in_file) : NULL; ta->next = ctx->type_aliases; ctx->type_aliases = ta; } const char *find_type_alias(ParserContext *ctx, const char *alias) +{ + TypeAlias *ta = find_type_alias_node(ctx, alias); + return ta ? ta->original_type : NULL; +} + +TypeAlias *find_type_alias_node(ParserContext *ctx, const char *alias) { TypeAlias *ta = ctx->type_aliases; while (ta) { if (strcmp(ta->alias, alias) == 0) { - return ta->original_type; + // printf("DEBUG: Found Alias '%s' (Opaque: %d)\n", alias, ta->is_opaque); + return ta; } ta = ta->next; } diff --git a/src/zprep.h b/src/zprep.h index e248871..a943f3f 100644 --- a/src/zprep.h +++ b/src/zprep.h @@ -108,6 +108,7 @@ typedef enum TOK_PREPROC, ///< Preprocessor directive (#...). TOK_ALIAS, ///< 'alias' keyword. TOK_COMMENT, ///< Comment (usually skipped). + TOK_OPAQUE, ///< 'opaque' keyword. TOK_UNKNOWN ///< Unknown token. } TokenType; diff --git a/tests/features/_opaque_alias_lib.zc b/tests/features/_opaque_alias_lib.zc new file mode 100644 index 0000000..7ca4abc --- /dev/null +++ b/tests/features/_opaque_alias_lib.zc @@ -0,0 +1,13 @@ +opaque alias Handle = int; + +fn new_handle(v: int) -> Handle { + return v; // Implicit cast int -> Handle (OK in module) +} + +fn get_val(h: Handle) -> int { + return h; // Implicit cast Handle -> int (OK in module) +} + +fn compare_handles(a: Handle, b: Handle) -> bool { + return a == b; // Strict equality (OK) +} diff --git a/tests/features/_opaque_lib.zc b/tests/features/_opaque_lib.zc new file mode 100644 index 0000000..de4d4c4 --- /dev/null +++ b/tests/features/_opaque_lib.zc @@ -0,0 +1,15 @@ +opaque struct SecretBox { + value: int; +} + +fn new_box(v: int) -> SecretBox { + return SecretBox { value: v }; +} + +fn get_value(b: SecretBox*) -> int { + return b.value; +} + +fn set_value(b: SecretBox*, v: int) { + b.value = v; +} diff --git a/tests/features/test_opaque.zc b/tests/features/test_opaque.zc new file mode 100644 index 0000000..5c84b2a --- /dev/null +++ b/tests/features/test_opaque.zc @@ -0,0 +1,18 @@ +import "_opaque_lib.zc"; + +fn main() { + let b = new_box(42); + + // Stack allocation should work (size known) + let b2: SecretBox; + b2 = b; + + // Public methods should work + let v = get_value(&b2); + assert(v == 42, "Value should be 42"); + + set_value(&b2, 100); + assert(get_value(&b2) == 100, "Value should be 100"); + + println "Opaque struct test passed"; +} diff --git a/tests/features/test_opaque_alias.zc b/tests/features/test_opaque_alias.zc new file mode 100644 index 0000000..062fa1f --- /dev/null +++ b/tests/features/test_opaque_alias.zc @@ -0,0 +1,13 @@ +import "_opaque_alias_lib.zc"; + +fn main() { + let h = new_handle(42); + let v = get_val(h); + + assert(v == 42, "Opaque Alias FAIL"); + + let h2 = new_handle(42); + assert(compare_handles(h, h2), "Equality FAIL"); + + println "Opaque Alias OK"; +} -- cgit v1.2.3 From fc6ff10acb9d00ea1c8c5924869e0efbd38093c5 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Thu, 29 Jan 2026 13:17:30 +0000 Subject: Objective-C interop + a few improvements --- README.md | 64 ++++++++- docs/lex.md | 130 +++++++++++++++++++ docs/std/README.md | 6 + docs/std/json.md | 57 ++++++++ docs/std/net.md | 44 +++++++ docs/std/set.md | 38 ++++++ docs/std/stack.md | 38 ++++++ docs/std/thread.md | 47 +++++++ docs/std/time.md | 38 ++++++ examples/objc_interop.zc | 42 ++++++ src/codegen/codegen_decl.c | 19 +-- src/codegen/compat.h | 47 +++++++ src/main.c | 8 ++ src/utils/utils.c | 223 +++++++++++++++++++++----------- src/zprep.h | 1 + std/fs.zc | 46 +++---- std/json.zc | 7 + std/map.zc | 7 + std/net.zc | 23 +++- std/thread.zc | 9 +- std/time.zc | 26 ++-- tests/features/test_build_directives.zc | 21 +-- 22 files changed, 793 insertions(+), 148 deletions(-) create mode 100644 docs/lex.md create mode 100644 docs/std/json.md create mode 100644 docs/std/net.md create mode 100644 docs/std/set.md create mode 100644 docs/std/stack.md create mode 100644 docs/std/thread.md create mode 100644 docs/std/time.md create mode 100644 examples/objc_interop.zc (limited to 'tests') diff --git a/README.md b/README.md index a35e0de..e295eb5 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [Building with Zig](#building-with-zig) - [C++ Interop](#c-interop) - [CUDA Interop](#cuda-interop) + - [Objective-C Interop](#objective-c-interop) - [Contributing](#contributing) - [Attributions](#attributions) @@ -929,7 +930,7 @@ Decorate functions and structs to modify compiler behavior. | `@derive(...)` | Struct | Auto-implement traits. Supports `Debug`, `Eq` (Smart Derive), `Copy`, `Clone`. | | `@` | Any | Passes generic attributes to C (e.g. `@flatten`, `@alias("name")`). | -### Custom Attributes +#### Custom Attributes Zen C supports a powerful **Custom Attribute** system that allows you to use any GCC/Clang `__attribute__` directly in your code. Any attribute that is not explicitly recognized by the Zen C compiler is treated as a generic attribute and passed through to the generated C code. @@ -941,7 +942,7 @@ Zen C attributes are mapped directly to C attributes: - `@name(args)` → `__attribute__((name(args)))` - `@name("string")` → `__attribute__((name("string")))` -### Smart Derives +#### Smart Derives Zen C provides "Smart Derives" that respect Move Semantics: @@ -1007,12 +1008,33 @@ Zen C supports special comments at the top of your source file to configure the | `//> link:` | `-lfoo` or `path/to/lib.a` | Link against a library or object file. | | `//> lib:` | `path/to/libs` | Add a library search path (`-L`). | | `//> include:` | `path/to/headers` | Add an include search path (`-I`). | +| `//> framework:` | `Cocoa` | Link against a macOS framework. | | `//> cflags:` | `-Wall -O3` | Pass arbitrary flags to the C compiler. | | `//> define:` | `MACRO` or `KEY=VAL` | Define a preprocessor macro (`-D`). | | `//> pkg-config:` | `gtk+-3.0` | Run `pkg-config` and append `--cflags` and `--libs`. | | `//> shell:` | `command` | Execute a shell command during the build. | | `//> get:` | `http://url/file` | Download a file if specific file does not exist. | +#### Features + +**1. OS Guarding** +Prefix directives with an OS name to apply them only on specific platforms. +Supported prefixes: `linux:`, `windows:`, `macos:` (or `darwin:`). + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. Environment Variable Expansion** +Use `${VAR}` syntax to expand environment variables in your directives. + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + #### Examples ```zc @@ -1072,6 +1094,12 @@ Zen C includes a standard library (`std`) covering essential functionality. | **`std/result.zc`** | Error handling (`Ok`/`Err`). | [Docs](docs/std/result.md) | | **`std/path.zc`** | Cross-platform path manipulation. | [Docs](docs/std/path.md) | | **`std/env.zc`** | Process environment variables. | [Docs](docs/std/env.md) | +| **`std/net.zc`** | TCP networking (Sockets). | [Docs](docs/std/net.md) | +| **`std/thread.zc`** | Threads and Synchronization. | [Docs](docs/std/thread.md) | +| **`std/time.zc`** | Time measurement and sleep. | [Docs](docs/std/time.md) | +| **`std/json.zc`** | JSON parsing and serialization. | [Docs](docs/std/json.md) | +| **`std/stack.zc`** | LIFO Stack `Stack`. | [Docs](docs/std/stack.md) | +| **`std/set.zc`** | Generic Hash Set `Set`. | [Docs](docs/std/set.md) | --- @@ -1302,6 +1330,38 @@ let tid = local_id(); > **Note:** The `--cuda` flag sets `nvcc` as the compiler and implies `--cpp` mode. Requires the NVIDIA CUDA Toolkit. +### Objective-C Interop + +Zen C can compile to Objective-C (`.m`) using the `--objc` flag, allowing you to use Objective-C frameworks (like Cocoa/Foundation) and syntax. + +```bash +# Compile with clang (or gcc/gnustep) +zc app.zc --objc --cc clang +``` + +#### Using Objective-C in Zen C + +Use `include` for headers and `raw` blocks for Objective-C syntax (`@interface`, `[...]`, `@""`). + +```zc +//> macos: framework: Foundation +//> linux: cflags: -fconstant-string-class=NSConstantString -D_NATIVE_OBJC_EXCEPTIONS +//> linux: link: -lgnustep-base -lobjc + +include + +fn main() { + raw { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSLog(@"Hello from Objective-C!"); + [pool drain]; + } + println "Zen C works too!"; +} +``` + +> **Note:** Zen C string interpolation works with Objective-C objects (`id`) by calling `debugDescription` or `description`. + --- ## Contributing diff --git a/docs/lex.md b/docs/lex.md new file mode 100644 index 0000000..1cd70fd --- /dev/null +++ b/docs/lex.md @@ -0,0 +1,130 @@ +# Lexical Structure + +## Source Text + +Zen-C source code is encoded in UTF-8. + +## Grammar Notation + +The lexical grammar is defined using a notation similar to EBNF. +- `Rule ::= Production`: Defines a rule. +- `[ ... ]`: Character class. +- `*`: Zero or more repetitions. +- `+`: One or more repetitions. +- `?`: Zero or one occurrence. +- `|`: Alternation. +- `"..."` or `'...'`: Literal string/character. +- `~`: Negation (e.g., `~[\n]` means any character except newline). + +## Whitespace and Comments + +Whitespace separates tokens but is otherwise ignored. Comments are treated as whitespace. + +```text +Whitespace ::= [ \t\n\r]+ +Comment ::= LineComment | BlockComment + +LineComment ::= "//" ~[\n]* +BlockComment ::= "/*" (BlockComment | ~("*/"))* "*/" +``` + +## Identifiers + +Identifiers name entities such as variables, functions, and types. + +```text +Identifier ::= IdentifierStart IdentifierPart* +IdentifierStart ::= [a-zA-Z_] +IdentifierPart ::= [a-zA-Z0-9_] +``` + +## Literals + +### Integer Literals + +Integers can be decimal, hexadecimal, or binary. + +```text +IntegerLiteral ::= ( DecimalInt | HexInt | BinaryInt ) IntegerSuffix? + +DecimalInt ::= [0-9]+ +HexInt ::= "0x" [0-9a-fA-F]+ +BinaryInt ::= "0b" [01]+ + +IntegerSuffix ::= "u" | "L" | "u64" | ... +``` +*Note: The lexer technically consumes any alphanumeric sequence following a number as a suffix.* + +### Floating Point Literals + +```text +FloatLiteral ::= [0-9]+ "." [0-9]* FloatSuffix? + | [0-9]+ FloatSuffix + +FloatSuffix ::= "f" +``` + +### String Literals + +```text +StringLiteral ::= '"' StringChar* '"' +StringChar ::= ~["\\] | EscapeSequence +EscapeSequence ::= "\\" ( ["\\/bfnrt] | "u" HexDigit{4} ) +``` + +### F-Strings + +```text +FStringLiteral ::= 'f"' StringChar* '"' +``` + + +### Character Literals + +```text +CharLiteral ::= "'" ( ~['\\] | EscapeSequence ) "'" +``` + +## Keywords + +```text +Keyword ::= Declaration | Control | Special | BoolLiteral | NullLiteral | LogicOp + +Declaration ::= "let" | "def" | "fn" | "struct" | "enum" | "union" | "alias" + | "trait" | "impl" | "use" | "module" | "import" | "opaque" + +Control ::= "if" | "else" | "match" | "for" | "while" | "loop" + | "return" | "break" | "continue" | "guard" | "unless" + | "defer" | "async" | "await" | "try" | "catch" | "goto" + +Special ::= "asm" | "assert" | "test" | "sizeof" | "embed" | "comptime" + | "autofree" | "volatile" | "launch" | "ref" | "static" | "const" + +BoolLiteral ::= "true" | "false" +NullLiteral ::= "null" + +CReserved ::= "auto" | "case" | "char" | "default" | "do" | "double" + | "extern" | "float" | "inline" | "int" | "long" | "register" + | "restrict" | "short" | "signed" | "switch" | "typedef" + | "unsigned" | "void" | "_Atomic" | "_Bool" | "_Complex" + | "_Generic" | "_Imaginary" | "_lmaginary" | "_Noreturn" + | "_Static_assert" | "_Thread_local" + +LogicOp ::= "and" | "or" +``` + +## Operators and Punctuation + +```text +Operator ::= "+" | "-" | "*" | "/" | "%" + | "&&" | "||" | "!" | "++" | "--" + | "&" | "|" | "^" | "~" | "<<" | ">>" + | "==" | "!=" | "<" | ">" | "<=" | ">=" + | "=" | "+=" | "-=" | "*=" | "/=" | "%=" + | "&=" | "|=" | "^=" | "<<=" | ">>=" + | ".." | "..=" | "..<" | "..." + | "." | "?." | "??" | "??=" | "->" | "=>" + | "::" | "|>" | "?" + | "(" | ")" | "{" | "}" | "[" | "]" + | "," | ":" | ";" | "@" +``` diff --git a/docs/std/README.md b/docs/std/README.md index 6125a4e..16ffc74 100644 --- a/docs/std/README.md +++ b/docs/std/README.md @@ -3,10 +3,16 @@ - [Env (Environment)](./env.md) - Process environment variables. - [File System (FS)](./fs.md) - File I/O and directory operations. - [IO](./io.md) - Standard Input/Output. +- [JSON](./json.md) - JSON parsing and serialization. - [Map](./map.md) - Hash map implementation. +- [Networking (Net)](./net.md) - TCP networking. - [Option](./option.md) - Optional values (Some/None). - [Path](./path.md) - File path manipulation. - [Result](./result.md) - Error handling (Ok/Err). - [Queue](./queue.md) - FIFO queue (Ring Buffer). +- [Set](./set.md) - Hash set implementation. +- [Stack](./stack.md) - LIFO stack. - [String](./string.md) - Growable, heap-allocated string type. +- [Thread (Concurrency)](./thread.md) - Multithreading and synchronization. +- [Time](./time.md) - Time measurement and sleep. - [Vector (Vec)](./vec.md) - A growable dynamic array. diff --git a/docs/std/json.md b/docs/std/json.md new file mode 100644 index 0000000..fba2ad8 --- /dev/null +++ b/docs/std/json.md @@ -0,0 +1,57 @@ +# JSON (`std/json.zc`) + +The `std/json` module provides a DOM-style JSON parser and builder. + +## Usage + +```zc +import "std/json.zc" +``` + +## Types + +### Struct `JsonValue` + +Represents a node in a JSON document. + +#### Creation Methods + +- **`fn null() -> JsonValue`**, **`fn null_ptr() -> JsonValue*`** +- **`fn bool(b: bool) -> JsonValue`**, **`fn bool_ptr(b: bool) -> JsonValue*`** +- **`fn number(n: double) -> JsonValue`**, **`fn number_ptr(n: double) -> JsonValue*`** +- **`fn string(s: char*) -> JsonValue`**, **`fn string_ptr(s: char*) -> JsonValue*`** +- **`fn array() -> JsonValue`**, **`fn array_ptr() -> JsonValue*`** +- **`fn object() -> JsonValue`**, **`fn object_ptr() -> JsonValue*`** + +#### Parsing + +- **`fn parse(json: char*) -> Result`** + Parses a JSON string into a heap-allocated `JsonValue` tree. + +#### Accessors + +- **`fn is_null(self) -> bool`**, **`is_bool`**, **`is_number`**, **`is_string`**, **`is_array`**, **`is_object`** + Check the type of the value. + +- **`fn as_string(self) -> Option`** + Returns `Some(string)` if the value is a string, `None` otherwise. +- **`fn as_int(self) -> Option`** +- **`fn as_float(self) -> Option`** +- **`fn as_bool(self) -> Option`** + +#### Object/Array Operations + +- **`fn push(self, val: JsonValue)`** + Appends a value to an array. +- **`fn set(self, key: char*, val: JsonValue)`** + Sets a key-value pair in an object. + +- **`fn get(self, key: char*) -> Option`** + Retrieves a value from an object by key. +- **`fn at(self, index: usize) -> Option`** + Retrieves a value from an array by index. + +#### Memory Management + +- **`fn free(self)`** + Recursively frees the JSON value and all its children. diff --git a/docs/std/net.md b/docs/std/net.md new file mode 100644 index 0000000..392c901 --- /dev/null +++ b/docs/std/net.md @@ -0,0 +1,44 @@ +# Networking (`std/net.zc`) + +The `std/net` module provides basic TCP networking capabilities. + +## Usage + +```zc +import "std/net.zc" +``` + +## Types + +### Type `TcpListener` + +Represents a TCP socket listening for incoming connections. + +#### Methods + +- **`fn bind(host: char*, port: int) -> Result`** + Creates a new listener bound to the specified host and port. + +- **`fn accept(self) -> Result`** + Blocks waiting for a new connection. Returns a `TcpStream` for the connected client. + +- **`fn close(self)`** + Closes the listening socket. + +### Type `TcpStream` + +Represents a TCP connection stream. + +#### Methods + +- **`fn connect(host: char*, port: int) -> Result`** + Connects to a remote host. + +- **`fn read(self, buf: char*, len: usize) -> Result`** + Reads up to `len` bytes into `buf`. Returns the number of bytes read. + +- **`fn write(self, buf: char*, len: usize) -> Result`** + Writes `len` bytes from `buf` to the stream. Returns the number of bytes written. + +- **`fn close(self)`** + Closes the connection. diff --git a/docs/std/set.md b/docs/std/set.md new file mode 100644 index 0000000..0d62a66 --- /dev/null +++ b/docs/std/set.md @@ -0,0 +1,38 @@ +# Set (`std/set.zc`) + +The `std/set` module provides a Generic Hash Set `Set`. + +## Usage + +```zc +import "std/set.zc" +``` + +## Types + +### Struct `Set` + +A set of unique elements. + +#### Methods + +- **`fn new() -> Set`** + Creates a new empty set. + +- **`fn add(self, val: T) -> bool`** + Adds a value to the set. Returns `true` if the value was added, `false` if it was already present. + +- **`fn contains(self, val: T) -> bool`** + Returns `true` if the set contains the value. + +- **`fn remove(self, val: T) -> bool`** + Removes a value from the set. Returns `true` if present and removed. + +- **`fn length(self) -> usize`** + Returns the number of elements in the set. + +- **`fn is_empty(self) -> bool`** + Returns `true` if the set is empty. + +- **`fn clear(self)`** + Removes all elements from the set. diff --git a/docs/std/stack.md b/docs/std/stack.md new file mode 100644 index 0000000..6e5da84 --- /dev/null +++ b/docs/std/stack.md @@ -0,0 +1,38 @@ +# Stack (`std/stack.zc`) + +The `std/stack` module provides a LIFO (Last-In, First-Out) stack data structure. + +## Usage + +```zc +import "std/stack.zc" +``` + +## Types + +### Struct `Stack` + +A generic stack. + +#### Methods + +- **`fn new() -> Stack`** + Creates a new empty stack. + +- **`fn push(self, value: T)`** + Pushes a value onto the top of the stack. + +- **`fn pop(self) -> Option`** + Removes and returns the top element of the stack. Returns `None` if empty. + +- **`fn length(self) -> usize`** + Returns the number of elements in the stack. + +- **`fn is_empty(self) -> bool`** + Returns `true` if the stack contains no elements. + +- **`fn clear(self)`** + Removes all elements from the stack. + +- **`fn clone(self) -> Stack`** + Creates a deep copy of the stack. diff --git a/docs/std/thread.md b/docs/std/thread.md new file mode 100644 index 0000000..6ac7e29 --- /dev/null +++ b/docs/std/thread.md @@ -0,0 +1,47 @@ +# Concurrency (`std/thread.zc`) + +The `std/thread` module provides primitives for multithreading and synchronization. + +## Usage + +```zc +import "std/thread.zc" +``` + +## Functions + +- **`fn sleep_ms(ms: int)`** + Sleeps the current thread for the specified number of milliseconds. + +## Types + +### Type `Thread` + +Represents a handle to a spawned thread. + +#### Methods + +- **`fn spawn(func: fn()) -> Result`** + Spawns a new thread executing the provided function. + > Note: Currently supports void functions with no arguments. + +- **`fn join(self) -> Result`** + Blocks the current thread until the spawned thread finishes. + +### Type `Mutex` + +A mutual exclusion primitive for protecting shared data. + +#### Methods + +- **`fn new() -> Mutex`** + Creates a new mutex. + +- **`fn lock(self)`** + Acquires the lock. Blocks if the lock is already held. + +- **`fn unlock(self)`** + Releases the lock. + +- **`fn free(self)`** + Destroys the mutex and frees associated resources. diff --git a/docs/std/time.md b/docs/std/time.md new file mode 100644 index 0000000..97dd208 --- /dev/null +++ b/docs/std/time.md @@ -0,0 +1,38 @@ +# Time (`std/time.zc`) + +The `std/time` module provides functionality for measuring time and sleeping. + +## Usage + +```zc +import "std/time.zc" +``` + +## Structs + +### Struct `Duration` + +Represents a span of time in milliseconds. + +#### Methods + +- **`fn from_ms(ms: U64) -> Duration`** + Creates a duration from milliseconds. + +- **`fn from_secs(s: U64) -> Duration`** + Creates a duration from seconds. + +### Struct `Time` + +Utilities for time manipulation. + +#### Methods + +- **`fn now() -> U64`** + Returns the current system time in milliseconds since the epoch. + +- **`fn sleep(d: Duration)`** + Sleeps for the specified duration. + +- **`fn sleep_ms(ms: U64)`** + Sleeps for the specified number of milliseconds. diff --git a/examples/objc_interop.zc b/examples/objc_interop.zc new file mode 100644 index 0000000..c33fd0d --- /dev/null +++ b/examples/objc_interop.zc @@ -0,0 +1,42 @@ + +//> macos: framework: Foundation +//> linux: cflags: -fconstant-string-class=NSConstantString -D_NATIVE_OBJC_EXCEPTIONS -I/usr/include/x86_64-linux-gnu/GNUstep -std=gnu99 +//> linux: link: -lgnustep-base -lobjc + +include + +raw { + id make_nsstring(const char* s) { + return [NSString stringWithUTF8String:s]; + } + + void print_description(id obj) { + NSLog(@"[ObjC Log] Description: %@", obj); + } +} + +fn main() { + + raw { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + } + + "=> Zen C + Objective-C interop demo."; + "=> (Make sure to run with: zc run --objc examples/objc_interop.zc)"; + + // Call ObjC helper to create an NSString (returned as id) + let s = make_nsstring("Hello from Objective-C!"); + + // Pass it back to ObjC + print_description(s); + + "Zen C interpolated string: {s}"; + + raw { + // You can also raw ObjC syntax anywhere. + NSArray *arr = [NSArray arrayWithObjects: @"Zen", @"Bit", @"C", nil]; + NSLog(@"[ObjC Raw] Array: %@", arr); + + [pool drain]; + } +} diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 11cdece..31513ef 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -91,14 +91,8 @@ void emit_preamble(ParserContext *ctx, FILE *out) fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : " "\"false\"; }\n", out); - fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", " - "signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", " - "unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", " - "long: \"%ld\", unsigned long: \"%lu\", long long: \"%lld\", " - "unsigned long long: \"%llu\", float: \"%f\", double: \"%f\", " - "char*: \"%s\", void*: \"%p\")\n", - out); - fputs("#define _z_arg(x) _Generic((x), _Bool: _z_bool_str(x), default: (x))\n", out); + fputs(ZC_C_GENERIC_STR, out); + fputs(ZC_C_ARG_GENERIC_STR, out); } fputs("typedef size_t usize;\ntypedef char* string;\n", out); @@ -108,12 +102,11 @@ void emit_preamble(ParserContext *ctx, FILE *out) fputs("typedef struct { pthread_t thread; void *result; } Async;\n", out); } fputs("typedef struct { void *func; void *ctx; } z_closure_T;\n", out); - fputs("#define U0 void\n#define I8 int8_t\n#define U8 uint8_t\n#define I16 " - "int16_t\n#define U16 uint16_t\n", + fputs("typedef void U0;\ntypedef int8_t I8;\ntypedef uint8_t U8;\ntypedef " + "int16_t I16;\ntypedef uint16_t U16;\n", out); - fputs("#define I32 int32_t\n#define U32 uint32_t\n#define I64 " - "int64_t\n#define U64 " - "uint64_t\n", + fputs("typedef int32_t I32;\ntypedef uint32_t U32;\ntypedef int64_t I64;\ntypedef " + "uint64_t U64;\n", out); fputs("#define F32 float\n#define F64 double\n", out); diff --git a/src/codegen/compat.h b/src/codegen/compat.h index 26b0df5..63a5af5 100644 --- a/src/codegen/compat.h +++ b/src/codegen/compat.h @@ -53,6 +53,27 @@ "#endif\n" \ "#endif\n" +/* Generic selection string for C mode */ +#define ZC_C_GENERIC_STR \ + "#ifdef __OBJC__\n" \ + "#define _z_objc_map ,id: \"%s\", Class: \"%s\", SEL: \"%s\"\n" \ + "#define _z_objc_arg_map(x) ,id: [(id)(x) description].UTF8String, Class: " \ + "class_getName((Class)(x)), SEL: sel_getName((SEL)(x))\n" \ + "#else\n" \ + "#define _z_objc_map\n" \ + "#define _z_objc_arg_map(x)\n" \ + "#endif\n" \ + "\n" \ + "#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", " \ + "signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", " \ + "unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", " \ + "long: \"%ld\", unsigned long: \"%lu\", long long: \"%lld\", " \ + "unsigned long long: \"%llu\", float: \"%f\", double: \"%f\", " \ + "char*: \"%s\", void*: \"%p\" _z_objc_map)\n" + +#define ZC_C_ARG_GENERIC_STR \ + "#define _z_arg(x) _Generic((x), _Bool: _z_bool_str(x) _z_objc_arg_map(x), default: (x))\n" + #ifdef __cplusplus #include @@ -126,6 +147,32 @@ inline const char *_zc_fmt(void *) } #define _z_str(x) _zc_fmt(x) + +#ifdef __OBJC__ +#include +#include +#include // for direct calls if needed, but [x description] is fine + +inline const char *_zc_fmt(id x) +{ + return [[x description] UTF8String]; +} +inline const char *_zc_fmt(Class x) +{ + return class_getName(x); +} +inline const char *_zc_fmt(SEL x) +{ + return sel_getName(x); +} +// BOOL is signed char usually, already handled? +// "typedef signed char BOOL;" on standard apple headers. +// If it maps to signed char, `_zc_fmt(signed char)` handles it ("%c"). +// We might want "YES"/"NO" for BOOL. +// But we can't distinguish typedefs in C++ function overloads easily if underlying type is same. +// We'll leave BOOL as %c or %d for now to avoid ambiguity errors. +#endif + #endif #endif diff --git a/src/main.c b/src/main.c index b6392cc..f5d8c1b 100644 --- a/src/main.c +++ b/src/main.c @@ -172,6 +172,10 @@ int main(int argc, char **argv) g_config.use_cuda = 1; g_config.use_cpp = 1; // CUDA implies C++ mode. } + else if (strcmp(arg, "--objc") == 0) + { + g_config.use_objc = 1; + } else if (strcmp(arg, "--check") == 0) { g_config.mode_check = 1; @@ -304,6 +308,10 @@ int main(int argc, char **argv) { temp_source_file = "out.cpp"; } + else if (g_config.use_objc) + { + temp_source_file = "out.m"; + } // Codegen to C/C++/CUDA FILE *out = fopen(temp_source_file, "w"); diff --git a/src/utils/utils.c b/src/utils/utils.c index 56a7690..d6d9853 100644 --- a/src/utils/utils.c +++ b/src/utils/utils.c @@ -533,9 +533,81 @@ char g_cflags[MAX_FLAGS_SIZE] = ""; int g_warning_count = 0; CompilerConfig g_config = {0}; +// Helper for environment expansion +static void expand_env_vars(char *dest, size_t dest_size, const char *src) +{ + char *d = dest; + const char *s = src; + size_t remaining = dest_size - 1; + + while (*s && remaining > 0) + { + if (*s == '$' && *(s + 1) == '{') + { + const char *end = strchr(s + 2, '}'); + if (end) + { + char var_name[256]; + int len = end - (s + 2); + if (len < 255) + { + strncpy(var_name, s + 2, len); + var_name[len] = 0; + char *val = getenv(var_name); + if (val) + { + size_t val_len = strlen(val); + if (val_len < remaining) + { + strcpy(d, val); + d += val_len; + remaining -= val_len; + s = end + 1; + continue; + } + } + } + } + } + *d++ = *s++; + remaining--; + } + *d = 0; +} + +// Helper to determine active OS +static int is_os_active(const char *os_name) +{ + if (0 == strcmp(os_name, "linux")) + { +#ifdef __linux__ + return 1; +#else + return 0; +#endif + } + else if (0 == strcmp(os_name, "windows")) + { +#ifdef _WIN32 + return 1; +#else + return 0; +#endif + } + else if (0 == strcmp(os_name, "macos") || 0 == strcmp(os_name, "darwin")) + { +#ifdef __APPLE__ + return 1; +#else + return 0; +#endif + } + return 0; +} + void scan_build_directives(ParserContext *ctx, const char *src) { - (void)ctx; // Currently unused, reserved for future use + (void)ctx; const char *p = src; while (*p) { @@ -554,103 +626,113 @@ void scan_build_directives(ParserContext *ctx, const char *src) len++; } - char line[2048]; + char raw_line[2048]; if (len >= 2047) { len = 2047; } - strncpy(line, start, len); - line[len] = 0; + strncpy(raw_line, start, len); + raw_line[len] = 0; + + char line[2048]; + expand_env_vars(line, sizeof(line), raw_line); - if (0 == strncmp(line, "link:", 5)) + char *directive = line; + char *colon = strchr(line, ':'); + if (colon) { - char *val = line + 5; - while (*val == ' ') + *colon = 0; + if (is_os_active(line)) + { + directive = colon + 1; + while (*directive == ' ') + { + directive++; + } + } + else if (0 == strcmp(line, "linux") || 0 == strcmp(line, "windows") || + 0 == strcmp(line, "macos")) { - val++; + goto next_line; } - if (strlen(g_link_flags) > 0) + else { - strcat(g_link_flags, " "); + *colon = ':'; + directive = line; } - strcat(g_link_flags, val); } - else if (0 == strncmp(line, "cflags:", 7)) + + // Process Directive + if (0 == strncmp(directive, "link:", 5)) { - char *val = line + 7; - while (*val == ' ') + if (strlen(g_link_flags) > 0) { - val++; + strcat(g_link_flags, " "); } + strcat(g_link_flags, directive + 5); + } + else if (0 == strncmp(directive, "cflags:", 7)) + { if (strlen(g_cflags) > 0) { strcat(g_cflags, " "); } - strcat(g_cflags, val); + strcat(g_cflags, directive + 7); } - else if (0 == strncmp(line, "include:", 8)) + else if (0 == strncmp(directive, "include:", 8)) { - char *val = line + 8; - while (*val == ' ') - { - val++; - } char flags[2048]; - sprintf(flags, "-I%s", val); + sprintf(flags, "-I%s", directive + 8); if (strlen(g_cflags) > 0) { strcat(g_cflags, " "); } strcat(g_cflags, flags); } - else if (strncmp(line, "lib:", 4) == 0) + else if (strncmp(directive, "lib:", 4) == 0) { - char *val = line + 4; - while (*val == ' ') - { - val++; - } char flags[2048]; - sprintf(flags, "-L%s", val); + sprintf(flags, "-L%s", directive + 4); if (strlen(g_link_flags) > 0) { strcat(g_link_flags, " "); } strcat(g_link_flags, flags); } - else if (strncmp(line, "define:", 7) == 0) + else if (strncmp(directive, "framework:", 10) == 0) { - char *val = line + 7; - while (*val == ' ') + char flags[2048]; + sprintf(flags, "-framework %s", directive + 10); + if (strlen(g_link_flags) > 0) { - val++; + strcat(g_link_flags, " "); } + strcat(g_link_flags, flags); + } + else if (strncmp(directive, "define:", 7) == 0) + { char flags[2048]; - sprintf(flags, "-D%s", val); + sprintf(flags, "-D%s", directive + 7); if (strlen(g_cflags) > 0) { strcat(g_cflags, " "); } strcat(g_cflags, flags); } - else if (0 == strncmp(line, "shell:", 6)) + else if (0 == strncmp(directive, "shell:", 6)) { - char *cmd = line + 6; - // printf("[zprep] Running shell: %s\n", cmd); - int res = system(cmd); - if (res != 0) + if (system(directive + 6) != 0) { - zwarn("Shell directive failed: %s", cmd); + zwarn("Shell directive failed: %s", directive + 6); } } - else if (strncmp(line, "get:", 4) == 0) + else if (strncmp(directive, "get:", 4) == 0) { - char *url = line + 4; + char *url = directive + 4; while (*url == ' ') { url++; } - char *filename = strrchr(url, '/'); if (!filename) { @@ -660,8 +742,6 @@ void scan_build_directives(ParserContext *ctx, const char *src) { filename++; } - - // Check if file exists to avoid redownloading. FILE *f = fopen(filename, "r"); if (f) { @@ -669,16 +749,13 @@ void scan_build_directives(ParserContext *ctx, const char *src) } else { - printf("[zprep] Downloading %s...\n", filename); char cmd[8192]; if (z_is_windows()) { - // On Windows, try curl which is often built-in now sprintf(cmd, "curl -s -L \"%s\" -o \"%s\"", url, filename); } else { - // Try wget, then curl. sprintf(cmd, "wget -q \"%s\" -O \"%s\" || curl -s -L \"%s\" -o \"%s\"", url, filename, url, filename); } @@ -688,40 +765,27 @@ void scan_build_directives(ParserContext *ctx, const char *src) } } } - else if (strncmp(line, "pkg-config:", 11) == 0) + else if (strncmp(directive, "pkg-config:", 11) == 0) { - char *libs = line + 11; - while (*libs == ' ') - { - libs++; - } - - if (z_is_windows()) - { - zwarn("pkg-config is usually not available on Windows. Build directive " - "'pkg-config:%s' might fail.", - libs); - } - + char *libs = directive + 11; char cmd[4096]; sprintf(cmd, "pkg-config --cflags %s", libs); FILE *fp = popen(cmd, "r"); if (fp) { - char flags[4096]; - flags[0] = 0; - if (fgets(flags, sizeof(flags), fp)) + char buf[1024]; + if (fgets(buf, sizeof(buf), fp)) { - int len = strlen(flags); - if (len > 0 && flags[len - 1] == '\n') + size_t l = strlen(buf); + if (l > 0 && buf[l - 1] == '\n') { - flags[len - 1] = 0; + buf[l - 1] = 0; } if (strlen(g_cflags) > 0) { strcat(g_cflags, " "); } - strcat(g_cflags, flags); + strcat(g_cflags, buf); } pclose(fp); } @@ -730,32 +794,35 @@ void scan_build_directives(ParserContext *ctx, const char *src) fp = popen(cmd, "r"); if (fp) { - char flags[4096]; - flags[0] = 0; - if (fgets(flags, sizeof(flags), fp)) + char buf[1024]; + if (fgets(buf, sizeof(buf), fp)) { - int len = strlen(flags); - if (len > 0 && flags[len - 1] == '\n') + size_t l = strlen(buf); + if (l > 0 && buf[l - 1] == '\n') { - flags[len - 1] = 0; + buf[l - 1] = 0; } if (strlen(g_link_flags) > 0) { strcat(g_link_flags, " "); } - strcat(g_link_flags, flags); + strcat(g_link_flags, buf); } pclose(fp); } } + else + { + zwarn("Unknown build directive: '%s'", directive); + } p += len; } + next_line: while (*p && *p != '\n') { p++; } - if (*p == '\n') { p++; diff --git a/src/zprep.h b/src/zprep.h index a943f3f..84400b3 100644 --- a/src/zprep.h +++ b/src/zprep.h @@ -359,6 +359,7 @@ typedef struct int mode_transpile; ///< 1 if 'transpile' command (to C). int use_cpp; ///< 1 if --cpp (emit C++ compatible code). int use_cuda; ///< 1 if --cuda (emit CUDA-compatible code). + int use_objc; ///< 1 if --objc (emit Objective-C compatible code). // GCC Flags accumulator. char gcc_flags[4096]; ///< Flags passed to the backend compiler. diff --git a/std/fs.zc b/std/fs.zc index c19f9f1..7a54005 100644 --- a/std/fs.zc +++ b/std/fs.zc @@ -2,21 +2,23 @@ import "./core.zc" import "./result.zc" import "./string.zc" import "./vec.zc" +import "./mem.zc" def Z_SEEK_SET = 0; def Z_SEEK_END = 2; def Z_F_OK = 0; +include +include +include +include +include // TODO: restructure this tomorrow. raw { - #include - #include - #include - - // typedef needed for Vec generation if inferred typedef struct DirEntry* DirEntryPtr; + // Wrappers for FILE* handling due to opaque pointer casting void* _z_fs_fopen(char* path, char* mode) { return fopen(path, mode); } @@ -40,7 +42,9 @@ raw { int64_t _z_fs_ftell(void* stream) { return (int64_t)ftell((FILE*)stream); } - + + // Wrappers needed because C headers declare these with 'const char*' + // but Zen C externs generate 'char*', leading to conflicting types. int _z_fs_access(char* pathname, int mode) { return access(pathname, mode); } @@ -53,6 +57,7 @@ raw { return rmdir(pathname); } + // Wrappers for DIR* handling void* _z_fs_opendir(char* name) { return opendir(name); } @@ -61,14 +66,7 @@ raw { return closedir((DIR*)dir); } - void* _z_fs_malloc(size_t size) { - return malloc(size); - } - - void _z_fs_free(void* ptr) { - free(ptr); - } - + // struct stat / struct dirent helpers int _z_fs_get_metadata(char* path, uint64_t* size, int* is_dir, int* is_file) { struct stat st; if (stat(path, &st) != 0) return -1; @@ -96,6 +94,10 @@ raw { } } +// Direct externs +extern fn malloc(size: usize) -> void*; +extern fn free(ptr: void*); + extern fn _z_fs_mkdir(path: char*) -> int; extern fn _z_fs_get_metadata(path: char*, size: U64*, is_dir: int*, is_file: int*) -> int; extern fn _z_fs_read_entry(dir: void*, out_name: char*, buf_size: int, is_dir: int*) -> int; @@ -105,13 +107,13 @@ extern fn _z_fs_fread(ptr: void*, size: usize, nmemb: usize, stream: void*) -> u extern fn _z_fs_fwrite(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; extern fn _z_fs_fseek(stream: void*, offset: long, whence: int) -> int; extern fn _z_fs_ftell(stream: void*) -> long; +extern fn _z_fs_opendir(name: char*) -> void*; +extern fn _z_fs_closedir(dir: void*) -> int; + extern fn _z_fs_access(pathname: char*, mode: int) -> int; extern fn _z_fs_unlink(pathname: char*) -> int; extern fn _z_fs_rmdir(pathname: char*) -> int; -extern fn _z_fs_opendir(name: char*) -> void*; -extern fn _z_fs_closedir(dir: void*) -> int; -extern fn _z_fs_malloc(size: usize) -> void*; -extern fn _z_fs_free(ptr: void*); + struct File { handle: void*; @@ -153,7 +155,7 @@ impl File { let size = _z_fs_ftell(self.handle); _z_fs_fseek(self.handle, 0, Z_SEEK_SET); - let buffer: char* = _z_fs_malloc((usize)size + 1); + let buffer: char* = malloc((usize)size + 1); if (buffer == NULL) { return Result::Err("Out of memory"); } @@ -162,7 +164,7 @@ impl File { buffer[read] = 0; let s = String::new(buffer); - _z_fs_free(buffer); + free(buffer); let res = Result::Ok(s); s.forget(); @@ -248,7 +250,7 @@ impl File { } let entries = Vec::new(); - let name_buf: char* = _z_fs_malloc(256); + let name_buf: char* = malloc(256); if (name_buf == NULL) { _z_fs_closedir(dir); @@ -277,7 +279,7 @@ impl File { ent.name.forget(); } - _z_fs_free(name_buf); + free(name_buf); _z_fs_closedir(dir); let res = Result< Vec >::Ok(entries); diff --git a/std/json.zc b/std/json.zc index e6a15cd..d373ab9 100644 --- a/std/json.zc +++ b/std/json.zc @@ -5,6 +5,7 @@ import "./map.zc" import "./string.zc" import "./result.zc" import "./option.zc" +import "./mem.zc" @derive(Eq) enum JsonType { @@ -450,3 +451,9 @@ impl JsonValue { } } } + +impl Drop for JsonValue { + fn drop(self) { + self.free(); + } +} diff --git a/std/map.zc b/std/map.zc index 8e58cdc..70d6ad2 100644 --- a/std/map.zc +++ b/std/map.zc @@ -1,6 +1,7 @@ import "./core.zc" import "./option.zc" +import "./mem.zc" raw { extern size_t __zen_hash_seed; @@ -248,3 +249,9 @@ impl Map { }; } } + +impl Drop for Map { + fn drop(self) { + self.free(); + } +} diff --git a/std/net.zc b/std/net.zc index 72c3fcb..dd10642 100644 --- a/std/net.zc +++ b/std/net.zc @@ -8,6 +8,7 @@ include import "./core.zc" import "./result.zc" import "./string.zc" +import "./mem.zc" def Z_AF_INET = 2; def Z_SOCK_STREAM = 1; @@ -36,15 +37,11 @@ raw { if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) return -2; return 0; } - + static int _z_net_accept(int fd) { return accept(fd, NULL, NULL); } - static ssize_t _z_net_read(int fd, char* buf, size_t n) { - return read(fd, (void*)buf, n); - } - static ssize_t _z_net_write(int fd, char* buf, size_t n) { return write(fd, (const void*)buf, n); } @@ -52,11 +49,11 @@ raw { extern fn socket(domain: int, type: int, proto: int) -> int; extern fn close(fd: int) -> int; +extern fn read(fd: int, buf: void*, count: usize) -> isize; extern fn _z_net_bind(fd: int, host: char*, port: int) -> int; extern fn _z_net_connect(fd: int, host: char*, port: int) -> int; extern fn _z_net_accept(fd: int) -> int; -extern fn _z_net_read(fd: int, buf: char*, n: usize) -> isize; extern fn _z_net_write(fd: int, buf: char*, n: usize) -> isize; @@ -66,7 +63,7 @@ struct TcpStream { impl TcpStream { fn read(self, buf: char*, len: usize) -> Result { - let n = _z_net_read(self.fd, buf, len); + let n = read(self.fd, (void*)buf, len); if (n < 0) return Result::Err("Read failed"); return Result::Ok((usize)n); } @@ -96,6 +93,12 @@ impl TcpStream { } } +impl Drop for TcpStream { + fn drop(self) { + self.close(); + } +} + struct TcpListener { fd: int; } @@ -126,3 +129,9 @@ impl TcpListener { } } } + +impl Drop for TcpListener { + fn drop(self) { + self.close(); + } +} diff --git a/std/thread.zc b/std/thread.zc index e90943b..98f080e 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -5,6 +5,7 @@ include import "./core.zc" import "./result.zc" +import "./mem.zc" raw { typedef void (*ZenThreadFunc)(void*); @@ -120,11 +121,17 @@ impl Mutex { if (self.handle) { _z_mutex_destroy(self.handle); free(self.handle); + self.handle = NULL; } } } +impl Drop for Mutex { + fn drop(self) { + self.free(); + } +} + fn sleep_ms(ms: int) { _z_usleep(ms * 1000); } - diff --git a/std/time.zc b/std/time.zc index 1191821..8e7cc6c 100644 --- a/std/time.zc +++ b/std/time.zc @@ -1,18 +1,28 @@ - import "./core.zc" +include +include +include +include + raw { - #include - #include - #include - static uint64_t _time_now_impl(void) { struct timeval tv; gettimeofday(&tv, NULL); return (uint64_t)(tv.tv_sec) * 1000 + (uint64_t)(tv.tv_usec) / 1000; } + + static long _z_time_time(void) { + return (long)time(NULL); + } } +extern fn srand(seed: U32); +extern fn rand() -> int; +extern fn usleep(micros: U32) -> int; +extern fn _time_now_impl() -> U64; +extern fn _z_time_time() -> long; + struct Duration { millis: U64; } @@ -33,10 +43,8 @@ extern size_t __zen_hash_seed; impl Time { fn randomize_hash() { - raw { - srand(time(NULL)); - __zen_hash_seed ^= (size_t)rand(); - } + srand((U32)_z_time_time()); + __zen_hash_seed ^= (size_t)rand(); } fn now() -> U64 { diff --git a/tests/features/test_build_directives.zc b/tests/features/test_build_directives.zc index 7edd317..d3f1cba 100644 --- a/tests/features/test_build_directives.zc +++ b/tests/features/test_build_directives.zc @@ -1,19 +1,8 @@ -//> link: -lm -//> cflags: -O2 -// Declare C math function (since we don't have a math stdlib module yet) -extern fn sin(x: double) -> double; +//> shell: echo "Env Worked" > ${PWD}/build_dir_env.txt +//> linux: shell: echo "Linux Worked" > ${PWD}/build_dir_linux.txt +//> windows: shell: echo "Windows Worked" > ${PWD}/build_dir_windows.txt -test "test_build_directives" { - println "Running Build Directives Test..."; - let x = 3.14159 / 2.0; // PI/2 - let s = sin(x); - // sin(PI/2) should be 1.0 - println "sin(PI/2) = {s}"; - - if (s > 0.99 && s < 1.01) { - println "Math Link Success!"; - } else { - println "Math Link Failure (Value wrong)"; - } +fn main() { + return 0; } -- cgit v1.2.3 From b27b128f97596236a4ce6a3d9b40ef3dfad84d06 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 00:34:33 +0000 Subject: New standard lib (std/process.zc). --- README.md | 1 + README_ES.md | 1 + README_ZH_CN.md | 1 + README_ZH_TW.md | 1 + docs/std/README.md | 1 + docs/std/process.md | 57 ++++++++++++++++++++ examples/process/exec.zc | 20 +++++++ std.zc | 1 + std/process.zc | 133 ++++++++++++++++++++++++++++++++++++++++++++++ tests/std/test_process.zc | 26 +++++++++ 10 files changed, 242 insertions(+) create mode 100644 docs/std/process.md create mode 100644 examples/process/exec.zc create mode 100644 std/process.zc create mode 100644 tests/std/test_process.zc (limited to 'tests') diff --git a/README.md b/README.md index 2745aba..75c4624 100644 --- a/README.md +++ b/README.md @@ -1106,6 +1106,7 @@ Zen C includes a standard library (`std`) covering essential functionality. | **`std/json.zc`** | JSON parsing and serialization. | [Docs](docs/std/json.md) | | **`std/stack.zc`** | LIFO Stack `Stack`. | [Docs](docs/std/stack.md) | | **`std/set.zc`** | Generic Hash Set `Set`. | [Docs](docs/std/set.md) | +| **`std/process.zc`** | Process execution and management. | [Docs](docs/std/process.md) | --- diff --git a/README_ES.md b/README_ES.md index 2735497..c5655e3 100644 --- a/README_ES.md +++ b/README_ES.md @@ -1106,6 +1106,7 @@ Zen C incluye una biblioteca estándar (`std`) que cubre las funcionalidades ese | **`std/json.zc`** | Parseo y serialización de JSON. | [Docs](docs/std/json.md) | | **`std/stack.zc`** | Pila LIFO `Stack`. | [Docs](docs/std/stack.md) | | **`std/set.zc`** | Conjunto Hash Genérico `Set`. | [Docs](docs/std/set.md) | +| **`std/process.zc`** | Ejecución y gestión de procesos. | [Docs](docs/std/process.md) | --- diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 52acb04..6fce2d2 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -1106,6 +1106,7 @@ Zen C 包含一个涵盖基本功能的标准库 (`std`)。 | **`std/json.zc`** | JSON 解析与序列化。 | [文档](docs/std/json.md) | | **`std/stack.zc`** | 后进先出栈 `Stack`。 | [文档](docs/std/stack.md) | | **`std/set.zc`** | 泛型哈希集合 `Set`。 | [文档](docs/std/set.md) | +| **`std/process.zc`** | 进程执行与管理。 | [文档](docs/std/process.md) | --- diff --git a/README_ZH_TW.md b/README_ZH_TW.md index b9b5511..fc9cef5 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -1106,6 +1106,7 @@ Zen C 包含一個涵蓋基本功能的標準庫 (`std`)。 | **`std/json.zc`** | JSON 解析與序列化。 | [文檔](docs/std/json.md) | | **`std/stack.zc`** | 後進先出棧 `Stack`。 | [文檔](docs/std/stack.md) | | **`std/set.zc`** | 泛型哈希集合 `Set`。 | [文檔](docs/std/set.md) | +| **`std/process.zc`** | 進程執行與管理。 | [文檔](docs/std/process.md) | --- diff --git a/docs/std/README.md b/docs/std/README.md index 16ffc74..3cbf8f8 100644 --- a/docs/std/README.md +++ b/docs/std/README.md @@ -8,6 +8,7 @@ - [Networking (Net)](./net.md) - TCP networking. - [Option](./option.md) - Optional values (Some/None). - [Path](./path.md) - File path manipulation. +- [Process](./process.md) - Process execution and management. - [Result](./result.md) - Error handling (Ok/Err). - [Queue](./queue.md) - FIFO queue (Ring Buffer). - [Set](./set.md) - Hash set implementation. diff --git a/docs/std/process.md b/docs/std/process.md new file mode 100644 index 0000000..31485ee --- /dev/null +++ b/docs/std/process.md @@ -0,0 +1,57 @@ +# Standard Library: Process (`std/process.zc`) + +The process module allows you to spawn and interact with child processes. + +## Usage + +```zc +import "std/process.zc" + +fn main() { + let output = Command::new("echo") + .arg("hello") + .output(); + + if (output.exit_code == 0) { + output.stdout.print(); + // Or access raw C string: output.stdout.c_str() + } +} +``` + +## Structs + +### Command + +A builder for spawning a process. + +```zc +struct Command { + program: String; + args: Vec; +} +``` + +#### Methods + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **new** | `Command::new(program: char*) -> Command` | Creates a new Command for the given program. | +| **arg** | `arg(self, arg: char*) -> Command*` | Adds an argument to the command. Returns the command pointer for chaining. | +| **output** | `output(self) -> Output` | Executes the command as a child process, waiting for it to finish and collecting all of its stdout. | +| **status** | `status(self) -> int` | Executes the command as a child process and returns the exit status code. Does not capture output (output goes to stdout/stderr). | + +### Output + +The output of a finished process. + +```zc +struct Output { + stdout: String; + exit_code: int; +} +``` + +#### Methods + +`Output` implements `Drop` to automatically free the captured `stdout` string. diff --git a/examples/process/exec.zc b/examples/process/exec.zc new file mode 100644 index 0000000..712a356 --- /dev/null +++ b/examples/process/exec.zc @@ -0,0 +1,20 @@ + +import "std/process.zc"; +import "std/string.zc"; + +fn main() { + "Executing 'ls -la' using std/process..."; + + let output = Command::new("ls") + .arg("-l") + .arg("-a") + .output(); + + if output.exit_code == 0 { + "--- Output ---"; + output.stdout.print(); + "--------------"; + } else { + !"Command failed with exit code: {output.exit_code}"; + } +} diff --git a/std.zc b/std.zc index 03bcd45..4793c11 100644 --- a/std.zc +++ b/std.zc @@ -18,4 +18,5 @@ import "./std/stack.zc" import "./std/queue.zc" import "./std/env.zc" import "./std/slice.zc" +import "./std/process.zc" diff --git a/std/process.zc b/std/process.zc new file mode 100644 index 0000000..d0b09a9 --- /dev/null +++ b/std/process.zc @@ -0,0 +1,133 @@ + +import "./core.zc"; +import "./vec.zc"; +import "./mem.zc"; +import "./string.zc"; +import "./option.zc"; + +raw { + void *_z_popen(char *command, char *type) { + return (void *)popen(command, type); + } + + int _z_pclose(void *stream) { + return pclose((FILE *)stream); + } + + char *_z_fgets(char *s, int size, void *stream) { + return fgets(s, size, (FILE *)stream); + } + + int _z_system(char *command) { + return system(command); + } +} + +extern fn _z_popen(command: char*, type: char*) -> void*; +extern fn _z_pclose(stream: void*) -> int; +extern fn _z_fgets(s: char*, size: int, stream: void*) -> char*; +extern fn _z_system(command: char*) -> int; + +struct Output { + stdout: String; + exit_code: int; +} + +struct Command { + program: String; + args: Vec; +} + +impl Command { + fn new(program: char*) -> Command { + return Command { + program: String::from(program), + args: Vec::new() + }; + } + + fn arg(self, arg: char*) -> Command* { + self.args.push(String::from(arg)); + return self; + } + + fn _build_cmd(self) -> String { + let cmd_str = self.program.substring(0, self.program.length()); + + for arg in &self.args { + let space = String::from(" "); + cmd_str.append(&space); + space.free(); + + cmd_str.append(arg); + } + + return cmd_str; + } + + fn output(self) -> Output { + let cmd_str = self._build_cmd(); + let cmd_c = cmd_str.c_str(); + + let fp = _z_popen(cmd_c, "r"); + + if (fp == 0) { + cmd_str.free(); + // TODO: Better error handling... + return Output { + stdout: String::from(""), + exit_code: -1 + }; + } + + let out = String::from(""); + let buf_size: usize = 1024; + let buf = (char*)malloc(buf_size); + + while (true) { + let res = _z_fgets(buf, (int)buf_size, fp); + if (res == 0) break; + + let chunk = String::from(buf); + out.append(&chunk); + chunk.free(); + } + + let code = _z_pclose(fp); + free(buf); + cmd_str.free(); + + return Output { + stdout: out, + exit_code: code + }; + } + + fn status(self) -> int { + let cmd_str = self._build_cmd(); + let code = _z_system(cmd_str.c_str()); + cmd_str.free(); + return code; + } + + fn free(self) { + self.program.free(); + + for s in &self.args { + s.free(); + } + self.args.free(); + } +} + +impl Drop for Command { + fn drop(self) { + self.free(); + } +} + +impl Drop for Output { + fn drop(self) { + self.stdout.free(); + } +} diff --git a/tests/std/test_process.zc b/tests/std/test_process.zc new file mode 100644 index 0000000..a3ba6ce --- /dev/null +++ b/tests/std/test_process.zc @@ -0,0 +1,26 @@ + +import "std/process.zc"; +import "std/string.zc"; + +test "process output" { + let cmd = Command::new("echo"); + cmd.arg("hello"); + + let out = cmd.output(); + + assert(out.exit_code == 0); + // echo usually outputs newline + assert(out.stdout.contains('h')); + assert(out.stdout.contains('e')); + assert(out.stdout.contains('l')); + assert(out.stdout.contains('o')); + + // out is dropped automatically + // cmd is dropped automatically +} + +test "process status" { + let cmd = Command::new("true"); // true command returns 0 + let status = cmd.status(); + assert(status == 0); +} -- cgit v1.2.3 From 422616a43b4f9b7659c96bfffd2f3095461dbea5 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 23:41:21 +0000 Subject: JSON serialization --- docs/std/json.md | 26 ++++++ docs/std/string.md | 4 + std/json.zc | 67 ++++++++++++++++ std/string.zc | 22 ++++++ tests/std/test_json_serialization.zc | 149 +++++++++++++++++++++++++++++++++++ 5 files changed, 268 insertions(+) create mode 100644 tests/std/test_json_serialization.zc (limited to 'tests') diff --git a/docs/std/json.md b/docs/std/json.md index fba2ad8..ce35d64 100644 --- a/docs/std/json.md +++ b/docs/std/json.md @@ -51,6 +51,32 @@ Represents a node in a JSON document. - **`fn at(self, index: usize) -> Option`** Retrieves a value from an array by index. +#### Serialization + +- **`fn to_string(self) -> String`** + Serializes the JSON value to a string representation. + +- **`fn stringify(self, buf: String*)`** + Internal recursive serialization method that appends to a string buffer. + +**Example:** +```zc +let obj = JsonValue::object(); +obj.set("name", JsonValue::string("Alice")); +obj.set("age", JsonValue::number(30.0)); + +let json_str = obj.to_string(); +println "{json_str.c_str()}"; // {"name":"Alice","age":30} +json_str.free(); +obj.free(); +``` + +**Features:** +- Proper escaping of special characters: `\"`, `\\`, `\n`, `\t`, `\r`, `\b`, `\f` +- Numbers formatted with `%.15g` for precision +- Recursive serialization for nested objects and arrays +- Round-trip compatible with `parse()` + #### Memory Management - **`fn free(self)`** diff --git a/docs/std/string.md b/docs/std/string.md index 1f89e0f..a2f63f5 100644 --- a/docs/std/string.md +++ b/docs/std/string.md @@ -49,8 +49,12 @@ struct String { | Method | Signature | Description | | :--- | :--- | :--- | | **append** | `append(self, other: String*)` | Appends another string to this one. | +| **append_c** | `append_c(self, s: char*)` | Appends a C string literal. Uses value receiver. | +| **append_c_ptr** | `append_c_ptr(ptr: String*, s: char*)` | Appends a C string literal using pointer receiver for guaranteed mutation. | | **add** | `add(self, other: String*) -> String` | Concatenates this string and another into a new String. | +**Note:** When passing `String*` to functions that need to mutate, use `append_c_ptr` instead of `append_c` for reliable mutation. + ### Access & Query | Method | Signature | Description | diff --git a/std/json.zc b/std/json.zc index d373ab9..70f7cf2 100644 --- a/std/json.zc +++ b/std/json.zc @@ -457,3 +457,70 @@ impl Drop for JsonValue { self.free(); } } + +extern fn sprintf(s: char*, fmt: const char*, ...) -> int; + +impl JsonValue { + fn to_string(self) -> String { + let s = String::new(""); + self.stringify(&s); + return s; + } + + fn stringify(self, buf: String*) { + if (self.kind.tag == JsonType::JSON_NULL().tag) { + buf.append_c_ptr("null"); + } else if (self.kind.tag == JsonType::JSON_BOOL().tag) { + if (self.bool_val) { buf.append_c_ptr("true"); } else { buf.append_c_ptr("false"); } + } else if (self.kind.tag == JsonType::JSON_NUMBER().tag) { + let tmp: char[64]; + sprintf((char*)tmp, "%.15g", self.number_val); // Use %.15g for precision + buf.append_c_ptr((char*)tmp); + } else if (self.kind.tag == JsonType::JSON_STRING().tag) { + buf.append_c_ptr("\""); + let p = self.string_val; + let len = strlen(p); + for (let i = 0; i < len; i = i + 1) { + let c = p[i]; + if (c == '"') buf.append_c_ptr("\\\""); + else if (c == '\\') buf.append_c_ptr("\\\\"); + else if (c == '\n') buf.append_c_ptr("\\n"); + else if (c == '\t') buf.append_c_ptr("\\t"); + else if (c == '\r') buf.append_c_ptr("\\r"); + else if (c == '\b') buf.append_c_ptr("\\b"); + else if (c == '\f') buf.append_c_ptr("\\f"); + else { + let tmp: char[2]; tmp[0] = c; tmp[1] = 0; + buf.append_c_ptr((char*)tmp); + } + } + buf.append_c_ptr("\""); + } else if (self.kind.tag == JsonType::JSON_ARRAY().tag) { + buf.append_c_ptr("["); + let v = self.array_val; + for (let i: usize = 0; i < v.length(); i = i + 1) { + if (i > 0) buf.append_c_ptr(","); + let item = v.get(i); + (*item).stringify(buf); + } + buf.append_c_ptr("]"); + } else if (self.kind.tag == JsonType::JSON_OBJECT().tag) { + buf.append_c_ptr("{{"); + let m = self.object_val; + let first = true; + for (let i: usize = 0; i < m.capacity(); i = i + 1) { + if (m.is_slot_occupied(i)) { + if (!first) buf.append_c_ptr(","); + first = false; + let key = m.key_at(i); + buf.append_c_ptr("\""); + buf.append_c_ptr(key); // Assuming keys are simple for now, but really should escape them too + buf.append_c_ptr("\":"); + let val = m.val_at(i); + val.stringify(buf); + } + } + buf.append_c_ptr("}"); + } + } +} diff --git a/std/string.zc b/std/string.zc index fe5b0ad..0bc9539 100644 --- a/std/string.zc +++ b/std/string.zc @@ -55,6 +55,28 @@ impl String { } } + fn append_c(self, s: char*) { + if (self.vec.len > 0) { + self.vec.len = self.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + self.vec.push(s[i]); + } + self.vec.push(0); + } + + fn append_c_ptr(ptr: String*, s: char*) { + if (ptr.vec.len > 0) { + ptr.vec.len = ptr.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + ptr.vec.push(s[i]); + } + ptr.vec.push(0); + } + fn add(self, other: String*) -> String { let new_s = String::from(self.c_str()); new_s.append(other); diff --git a/tests/std/test_json_serialization.zc b/tests/std/test_json_serialization.zc new file mode 100644 index 0000000..9fd5b32 --- /dev/null +++ b/tests/std/test_json_serialization.zc @@ -0,0 +1,149 @@ +import "std/json.zc" +import "std/io.zc" + +test "primitives" { + // Null + let v = JsonValue::null(); + let s = v.to_string(); + let expected = String::from("null"); + if (!s.eq(&expected)) { + panic("Null serialization failed"); + } + expected.free(); + s.free(); + + // Bool True + v = JsonValue::bool(true); + s = v.to_string(); + expected = String::from("true"); + if (!s.eq(&expected)) { + panic("Bool true serialization failed"); + } + expected.free(); + s.free(); + + // Bool False + v = JsonValue::bool(false); + s = v.to_string(); + expected = String::from("false"); + if (!s.eq(&expected)) { + panic("Bool false serialization failed"); + } + expected.free(); + s.free(); + + // Number Int + v = JsonValue::number(123.0); + s = v.to_string(); + expected = String::from("123"); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("Number 123 serialization failed"); + } + expected.free(); + s.free(); + + // Number Float + v = JsonValue::number(12.5); + s = v.to_string(); + expected = String::from("12.5"); + if (!s.eq(&expected)) { + panic("Number 12.5 serialization failed"); + } + expected.free(); + s.free(); + + // String Simple + v = JsonValue::string("hello"); + s = v.to_string(); + expected = String::from("\"hello\""); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("String hello serialization failed"); + } + expected.free(); + s.free(); + + // String Escaped + v = JsonValue::string("hello \"world\""); + s = v.to_string(); + expected = String::from("\"hello \\\"world\\\"\""); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("String escaped serialization failed"); + } + expected.free(); + s.free(); +} + +test "array" { + let v = JsonValue::array(); + v.push(JsonValue::number(1.0)); + v.push(JsonValue::bool(true)); + v.push(JsonValue::string("a")); + + let s = v.to_string(); + let expected = String::from("[1,true,\"a\"]"); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("Array serialization failed"); + } + expected.free(); + s.free(); +} + +test "object" { + let v = JsonValue::object(); + v.set("key", JsonValue::string("value")); + + let s = v.to_string(); + // Round trip verification to avoid parser bug with literals + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Object round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip not object"); + + let val_opt = (*parsed).get_string("key"); + if (val_opt.is_none()) panic("Round trip missing 'key'"); + + let val_str = val_opt.unwrap(); + if (strcmp(val_str, "value") != 0) panic("Round trip wrong value"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} + +test "nested" { + // {"arr":[1,2]} + let v = JsonValue::object(); + let arr = JsonValue::array(); + arr.push(JsonValue::number(1.0)); + arr.push(JsonValue::number(2.0)); + v.set("arr", arr); + + let s = v.to_string(); + + // Round trip + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip type mismatch"); + + let arr_opt = (*parsed).get_array("arr"); + if (arr_opt.is_none()) panic("Round trip missing arr"); + + let arr_ptr = arr_opt.unwrap(); + if (!(*arr_ptr).is_array()) panic("Inner not array"); + if ((*arr_ptr).len() != 2) panic("Wrong array length"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} \ No newline at end of file -- cgit v1.2.3 From 856c9fe56b412779e045ef86a767b93d5c7f563b Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 01:15:25 +0000 Subject: Improvements for slice + better iteration for arrays --- README.md | 11 ++- README_ES.md | 12 ++- README_ZH_CN.md | 27 ++++-- README_ZH_TW.md | 11 ++- docs/std/slice.md | 90 ++++++++++++++++++ src/codegen/codegen.c | 88 +++++++++++++++++- src/codegen/codegen_utils.c | 25 ++++- src/parser/parser_stmt.c | 152 ++++++++++++++++++++++++++++--- std/slice.zc | 35 +++++++ tests/std/test_direct_array_iteration.zc | 37 ++++++++ tests/std/test_slice_iteration.zc | 29 ++++++ 11 files changed, 479 insertions(+), 38 deletions(-) create mode 100644 docs/std/slice.md create mode 100644 tests/std/test_direct_array_iteration.zc create mode 100644 tests/std/test_slice_iteration.zc (limited to 'tests') diff --git a/README.md b/README.md index ab51b83..ecdf8fc 100644 --- a/README.md +++ b/README.md @@ -494,8 +494,15 @@ for i in 0..<10 { ... } // Exclusive (Explicit) for i in 0..=10 { ... } // Inclusive (0 to 10) for i in 0..10 step 2 { ... } -// Iterator (Vec, Array, or custom Iterable) -for item in collection { ... } +// Iterator (Vec or custom Iterable) +for item in vec { ... } + +// Iterate over fixed-size arrays directly +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val is int + println "{val}"; +} // While while x < 10 { ... } diff --git a/README_ES.md b/README_ES.md index d2cfbbb..d73e9ca 100644 --- a/README_ES.md +++ b/README_ES.md @@ -493,8 +493,15 @@ for i in 0..<10 { ... } // Exclusivo (Explícito) for i in 0..=10 { ... } // Inclusivo (0 al 10) for i in 0..10 step 2 { ... } -// Iterador (Vec, Array, o Iterable personalizado) -for item in coleccion { ... } +// Iterador (Vec o Iterable personalizado) +for item in vec { ... } + +// Iterar sobre arrays de tamaño fijo directamente +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val es int + println "{val}"; +} // While while x < 10 { ... } @@ -508,6 +515,7 @@ externo: loop { for _ in 0..5 { ... } ``` + #### Control Avanzado ```zc // Guard: Ejecuta else y retorna si la condición es falsa diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 2ac38a2..51689f6 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -485,26 +485,33 @@ match opt { } ``` -#### 循环 +#### 循環 ```zc -// 区间迭代 -for i in 0..10 { ... } // 左闭右开 (0 到 9) -for i in 0..<10 { ... } // 左闭右开 (显式) -for i in 0..=10 { ... } // 全闭 (0 到 10) +// 區間迭代 +for i in 0..10 { ... } // 左閉右開 (0 到 9) +for i in 0..<10 { ... } // 左閉右開 (顯式) +for i in 0..=10 { ... } // 全閉 (0 到 10) for i in 0..10 step 2 { ... } -// 迭代器 (Vec, Array, 或自定义 Iterable) -for item in collection { ... } +// 迭代器 (Vec 或自定義 Iterable) +for item in vec { ... } -// While 循环 +// 直接迭代固定大小数组 +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val 是 int + println "{val}"; +} + +// While 循環 while x < 10 { ... } -// 带标签的无限循环 +// 帶標籤的無限循環 outer: loop { if done { break outer; } } -// 重复 N 次 +// 重複 N 次 for _ in 0..5 { ... } ``` diff --git a/README_ZH_TW.md b/README_ZH_TW.md index 13591cf..6fa0dbd 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -493,8 +493,15 @@ for i in 0..<10 { ... } // 左閉右開 (顯式) for i in 0..=10 { ... } // 全閉 (0 到 10) for i in 0..10 step 2 { ... } -// 迭代器 (Vec, Array, 或自定義 Iterable) -for item in collection { ... } +// 迭代器 (Vec 或自定義 Iterable) +for item in vec { ... } + +// 直接迭代固定大小數組 +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val 是 int + println "{val}"; +} // While 循環 while x < 10 { ... } diff --git a/docs/std/slice.md b/docs/std/slice.md new file mode 100644 index 0000000..b70c5fe --- /dev/null +++ b/docs/std/slice.md @@ -0,0 +1,90 @@ +# Standard Library: Slice (`std/slice.zc`) + +`Slice` is a lightweight, non-owning view into a contiguous sequence of elements. It's particularly useful for working with fixed-size arrays and enabling iteration. + +## Usage + +```zc +import "std/slice.zc" + +fn main() { + let arr: int[5] = [1, 2, 3, 4, 5]; + + // Direct iteration (Recommended) + for val in arr { + println "{val}"; + } + + // Manual slice creation (for partial views or specific needs) + let slice = Slice::from_array((int*)(&arr), 5); + for val in slice { + println "{val}"; + } +} +``` + +## Structure + +```zc +struct Slice { + data: T*; + len: usize; +} +``` + +## Methods + +### Construction + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **from_array** | `Slice::from_array(arr: T*, len: usize) -> Slice` | Creates a slice view over an array. | + +### Iteration + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **iterator** | `iterator(self) -> SliceIter` | Returns an iterator for `for-in` loops. | + +`SliceIter` implements the iterator protocol with a `next() -> Option` method. + +### Access & Query + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **length** | `length(self) -> usize` | Returns the number of elements. | +| **is_empty** | `is_empty(self) -> bool` | Returns true if length is 0. | +| **get** | `get(self, idx: usize) -> Option` | Returns the element at index, or None if out of bounds. | +| **at** | `at(self, idx: usize) -> Option` | Alias for `get`. | + +## Examples + +### Iterating over fixed-size arrays + +```zc +let numbers: int[3] = [10, 20, 30]; +let slice = Slice::from_array((int*)(&numbers), 3); + +for n in slice { + println "Number: {n}"; +} +``` + +### Safe indexed access + +```zc +let arr: int[3] = [1, 2, 3]; +let slice = Slice::from_array((int*)(&arr), 3); + +let opt = slice.get(1); +if (!opt.is_none()) { + println "Value: {opt.unwrap()}"; // Prints: Value: 2 +} +``` + +## Notes + +- `Slice` does not own its data - it's just a view +- No memory management needed (no `free()` method) +- Must specify the generic type explicitly: `Slice`, `Slice`, etc. +- 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 a66f179..7a67428 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -65,6 +65,52 @@ static void codegen_var_expr(ParserContext *ctx, ASTNode *node, FILE *out) zwarn_at(node->token, "%s\n = help: %s", msg, help); } } + + // Check for static method call pattern: Type::method or Slice::method + char *double_colon = strstr(node->var_ref.name, "::"); + if (double_colon) + { + // Extract type name and method name + int type_len = double_colon - node->var_ref.name; + char *type_name = xmalloc(type_len + 1); + strncpy(type_name, node->var_ref.name, type_len); + type_name[type_len] = 0; + + char *method_name = double_colon + 2; // Skip :: + + // Handle generic types: Slice -> Slice_int + char mangled_type[256]; + if (strchr(type_name, '<')) + { + // Generic type - need to mangle it + char *lt = strchr(type_name, '<'); + char *gt = strchr(type_name, '>'); + + if (lt && gt) + { + // Extract base type and type argument + *lt = 0; + char *type_arg = lt + 1; + *gt = 0; + + sprintf(mangled_type, "%s_%s", type_name, type_arg); + } + else + { + strcpy(mangled_type, type_name); + } + } + else + { + strcpy(mangled_type, type_name); + } + + // Output as Type__method + fprintf(out, "%s__%s", mangled_type, method_name); + free(type_name); + return; + } + fprintf(out, "%s", node->var_ref.name); } @@ -348,6 +394,7 @@ 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); @@ -418,11 +465,43 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) base += 7; } + char *mangled_base = base; + char base_buf[256]; + + // Mangle generic types: Slice -> Slice_int, Vec -> Vec_Point + char *lt = strchr(base, '<'); + if (lt) + { + char *gt = strchr(lt, '>'); + if (gt) + { + int prefix_len = lt - base; + int arg_len = gt - lt - 1; + snprintf(base_buf, 255, "%.*s_%.*s", prefix_len, base, arg_len, lt + 1); + mangled_base = base_buf; + } + } + if (!strchr(type, '*') && target->type == NODE_EXPR_CALL) { - fprintf(out, "({ %s _t = ", type); + char *type_mangled = type; + char type_buf[256]; + char *t_lt = strchr(type, '<'); + if (t_lt) + { + char *t_gt = strchr(t_lt, '>'); + if (t_gt) + { + int p_len = t_lt - type; + int a_len = t_gt - t_lt - 1; + snprintf(type_buf, 255, "%.*s_%.*s", p_len, type, a_len, t_lt + 1); + type_mangled = type_buf; + } + } + + fprintf(out, "({ %s _t = ", type_mangled); codegen_expression(ctx, target, out); - fprintf(out, "; %s__%s(&_t", base, method); + fprintf(out, "; %s__%s(&_t", mangled_base, method); ASTNode *arg = node->call.args; while (arg) { @@ -435,10 +514,11 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else { // Mixin Lookup Logic - char *call_base = base; + char *call_base = mangled_base; + int need_cast = 0; char mixin_func_name[128]; - sprintf(mixin_func_name, "%s__%s", base, method); + sprintf(mixin_func_name, "%s__%s", call_base, method); char *resolved_method_suffix = NULL; diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 0d03661..8d9cb28 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -64,13 +64,28 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) } else if (generic && (!bracket || generic < bracket)) { - // Strip generic part for C output - int base_len = generic - type_str; - fprintf(out, "%.*s %s", base_len, type_str, name); + char *gt = strchr(generic, '>'); + if (gt) + { + int base_len = generic - type_str; + int arg_len = gt - generic - 1; + + fprintf(out, "%.*s_%.*s %s", base_len, type_str, arg_len, generic + 1, name); - if (bracket) + if (bracket) + { + fprintf(out, "%s", bracket); + } + } + else { - fprintf(out, "%s", bracket); + int base_len = generic - type_str; + fprintf(out, "%.*s %s", base_len, type_str, name); + + if (bracket) + { + fprintf(out, "%s", bracket); + } } } else if (bracket) diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index a471fe6..4c24de3 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -1133,6 +1133,7 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *obj_expr = start_expr; char *iter_method = "iterator"; + ASTNode *slice_decl = NULL; // Track if we need to add a slice declaration // Check for reference iteration: for x in &vec if (obj_expr->type == NODE_EXPR_UNARY && obj_expr->unary.op && @@ -1142,6 +1143,78 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) iter_method = "iter_ref"; } + // Check for array iteration: wrap with Slice::from_array + if (obj_expr->type_info && obj_expr->type_info->kind == TYPE_ARRAY && + obj_expr->type_info->array_size > 0) + { + // Create a var decl for the slice + slice_decl = ast_create(NODE_VAR_DECL); + slice_decl->var_decl.name = xstrdup("__zc_arr_slice"); + + // Build type string: Slice + char *elem_type_str = type_to_string(obj_expr->type_info->inner); + char slice_type[256]; + sprintf(slice_type, "Slice<%s>", elem_type_str); + slice_decl->var_decl.type_str = xstrdup(slice_type); + + ASTNode *from_array_call = ast_create(NODE_EXPR_CALL); + ASTNode *static_method = ast_create(NODE_EXPR_VAR); + + // The function name for static methods is Type::method format + char func_name[512]; + snprintf(func_name, 511, "%s::from_array", slice_type); + static_method->var_ref.name = xstrdup(func_name); + + from_array_call->call.callee = static_method; + + // Create arguments + ASTNode *arr_addr = ast_create(NODE_EXPR_UNARY); + arr_addr->unary.op = xstrdup("&"); + arr_addr->unary.operand = obj_expr; + + ASTNode *arr_cast = ast_create(NODE_EXPR_CAST); + char cast_type[256]; + sprintf(cast_type, "%s*", elem_type_str); + arr_cast->cast.target_type = xstrdup(cast_type); + arr_cast->cast.expr = arr_addr; + + ASTNode *size_arg = ast_create(NODE_EXPR_LITERAL); + size_arg->literal.type_kind = LITERAL_INT; + size_arg->literal.int_val = obj_expr->type_info->array_size; + char size_buf[32]; + sprintf(size_buf, "%d", obj_expr->type_info->array_size); + size_arg->literal.string_val = xstrdup(size_buf); + + arr_cast->next = size_arg; + from_array_call->call.args = arr_cast; + from_array_call->call.arg_count = 2; + + slice_decl->var_decl.init_expr = from_array_call; + + // Manually trigger generic instantiation for Slice + // This ensures that Slice_int, Slice_float, etc. structures are generated + Token dummy_tok = {0}; + instantiate_generic(ctx, "Slice", elem_type_str, elem_type_str, dummy_tok); + + // Instantiate SliceIter and Option too for the loop logic + char iter_type[256]; + sprintf(iter_type, "SliceIter<%s>", elem_type_str); + instantiate_generic(ctx, "SliceIter", elem_type_str, elem_type_str, dummy_tok); + + char option_type[256]; + sprintf(option_type, "Option<%s>", elem_type_str); + instantiate_generic(ctx, "Option", elem_type_str, elem_type_str, dummy_tok); + + // Replace obj_expr with a reference to the slice variable + ASTNode *slice_ref = ast_create(NODE_EXPR_VAR); + slice_ref->var_ref.name = xstrdup("__zc_arr_slice"); + slice_ref->resolved_type = + xstrdup(slice_type); // Explicitly set type for codegen + obj_expr = slice_ref; + + free(elem_type_str); + } + // var __it = obj.iterator(); ASTNode *it_decl = ast_create(NODE_VAR_DECL); it_decl->var_decl.name = xstrdup("__it"); @@ -1182,6 +1255,34 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) stmts_tail = node; \ } + char *iter_type_ptr = NULL; + char *option_type_ptr = NULL; + + if (slice_decl) + { + char *slice_t = slice_decl->var_decl.type_str; + char *start = strchr(slice_t, '<'); + if (start) + { + char *end = strrchr(slice_t, '>'); + if (end) + { + int len = end - start - 1; + char *elem = xmalloc(len + 1); + strncpy(elem, start + 1, len); + elem[len] = 0; + + iter_type_ptr = xmalloc(256); + sprintf(iter_type_ptr, "SliceIter<%s>", elem); + + option_type_ptr = xmalloc(256); + sprintf(option_type_ptr, "Option<%s>", elem); + + free(elem); + } + } + } + // var __opt = __it.next(); ASTNode *opt_decl = ast_create(NODE_VAR_DECL); opt_decl->var_decl.name = xstrdup("__opt"); @@ -1192,6 +1293,10 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_next = ast_create(NODE_EXPR_MEMBER); ASTNode *it_ref = ast_create(NODE_EXPR_VAR); it_ref->var_ref.name = xstrdup("__it"); + if (iter_type_ptr) + { + it_ref->resolved_type = xstrdup(iter_type_ptr); + } memb_next->member.target = it_ref; memb_next->member.field = xstrdup("next"); call_next->call.callee = memb_next; @@ -1204,15 +1309,22 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_is_none = ast_create(NODE_EXPR_MEMBER); ASTNode *opt_ref1 = ast_create(NODE_EXPR_VAR); opt_ref1->var_ref.name = xstrdup("__opt"); + if (option_type_ptr) + { + opt_ref1->resolved_type = xstrdup(option_type_ptr); + } memb_is_none->member.target = opt_ref1; memb_is_none->member.field = xstrdup("is_none"); call_is_none->call.callee = memb_is_none; + call_is_none->call.args = NULL; + call_is_none->call.arg_count = 0; - ASTNode *break_stmt = ast_create(NODE_BREAK); - + // if (__opt.is_none()) break; ASTNode *if_break = ast_create(NODE_IF); if_break->if_stmt.condition = call_is_none; + ASTNode *break_stmt = ast_create(NODE_BREAK); if_break->if_stmt.then_body = break_stmt; + if_break->if_stmt.else_body = NULL; APPEND_STMT(if_break); // var = __opt.unwrap(); @@ -1225,25 +1337,28 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_unwrap = ast_create(NODE_EXPR_MEMBER); ASTNode *opt_ref2 = ast_create(NODE_EXPR_VAR); opt_ref2->var_ref.name = xstrdup("__opt"); + if (option_type_ptr) + { + opt_ref2->resolved_type = xstrdup(option_type_ptr); + } memb_unwrap->member.target = opt_ref2; memb_unwrap->member.field = xstrdup("unwrap"); call_unwrap->call.callee = memb_unwrap; + call_unwrap->call.args = NULL; + call_unwrap->call.arg_count = 0; user_var_decl->var_decl.init_expr = call_unwrap; APPEND_STMT(user_var_decl); - // User Body + // User body statements enter_scope(ctx); add_symbol(ctx, var_name, NULL, NULL); - ASTNode *user_body_node; - if (lexer_peek(l).type == TOK_LBRACE) - { - user_body_node = parse_block(ctx, l); - } - else + // Body block + ASTNode *stmt = parse_statement(ctx, l); + ASTNode *user_body_node = stmt; + if (stmt && stmt->type != NODE_BLOCK) { - ASTNode *stmt = parse_statement(ctx, l); ASTNode *blk = ast_create(NODE_BLOCK); blk->block.statements = stmt; user_body_node = blk; @@ -1256,10 +1371,21 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) loop_body->block.statements = stmts_head; while_loop->while_stmt.body = loop_body; - // Wrap entire thing in a block to scope _it + // Wrap entire thing in a block to scope __it (and __zc_arr_slice if present) ASTNode *outer_block = ast_create(NODE_BLOCK); - it_decl->next = while_loop; - outer_block->block.statements = it_decl; + if (slice_decl) + { + // Chain: slice_decl -> it_decl -> while_loop + slice_decl->next = it_decl; + it_decl->next = while_loop; + outer_block->block.statements = slice_decl; + } + else + { + // Chain: it_decl -> while_loop + it_decl->next = while_loop; + outer_block->block.statements = it_decl; + } return outer_block; } diff --git a/std/slice.zc b/std/slice.zc index 778c6ed..7ace396 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -1,10 +1,45 @@ +import "./option.zc" + struct Slice { data: T*; len: usize; } +struct SliceIter { + data: T*; + count: usize; + idx: usize; +} + +impl SliceIter { + fn next(self) -> Option { + if (self.idx < self.count) { + let item = self.data[self.idx]; + self.idx = self.idx + 1; + return Option::Some(item); + } + return Option::None(); + } + + fn iterator(self) -> SliceIter { + return *self; + } +} + impl Slice { + fn from_array(arr: T*, len: usize) -> Slice { + return Slice { data: arr, len: len }; + } + + fn iterator(self) -> SliceIter { + return SliceIter { + data: self.data, + count: self.len, + idx: 0 + }; + } + fn length(self) -> usize { return self.len; } diff --git a/tests/std/test_direct_array_iteration.zc b/tests/std/test_direct_array_iteration.zc new file mode 100644 index 0000000..359951f --- /dev/null +++ b/tests/std/test_direct_array_iteration.zc @@ -0,0 +1,37 @@ +import "std/slice.zc" + +test "direct array iteration" { + let arr: int[5] = [1, 2, 3, 4, 5]; + + let sum = 0; + for val in arr { + sum = sum + val; + } + + assert(sum == 15, "Sum should be 1+2+3+4+5 = 15"); +} + +test "direct array iteration with different types" { + let floats: float[3] = [1.5, 2.5, 3.0]; + let count = 0; + + for f in floats { + count = count + 1; + } + + assert(count == 3, "Should iterate over all 3 elements"); +} + +// TODO: Nested array iteration needs special handling +// test "nested array iteration" { +// let matrix: int[2][3] = [[1, 2, 3], [4, 5, 6]]; +// let total = 0; +// +// for row in matrix { +// for val in row { +// total = total + val; +// } +// } +// +// assert(total == 21, "Sum should be 1+2+3+4+5+6 = 21"); +// } diff --git a/tests/std/test_slice_iteration.zc b/tests/std/test_slice_iteration.zc new file mode 100644 index 0000000..b7eddf4 --- /dev/null +++ b/tests/std/test_slice_iteration.zc @@ -0,0 +1,29 @@ +import "std/slice.zc" +import "std/io.zc" + +test "slice from array iteration" { + let ints: int[5] = [1, 2, 3, 4, 5]; + let slice = Slice::from_array((int*)(&ints), 5); + + // Test iteration + let sum = 0; + for val in slice { + sum = sum + val; + } + + if (sum != 15) { + panic("Slice iteration failed: expected sum 15"); + } +} + +test "slice methods" { + let arr: int[3] = [10, 20, 30]; + let slice = Slice::from_array((int*)(&arr), 3); + + if (slice.length() != 3) panic("Slice length wrong"); + if (slice.is_empty()) panic("Slice should not be empty"); + + let opt = slice.get(1); + if (opt.is_none()) panic("Slice get failed"); + if (opt.unwrap() != 20) panic("Slice get returned wrong value"); +} -- cgit v1.2.3 From ccc53b11a0e273f46cb40e5f0eb32a74ab6750bf Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 15:31:41 +0000 Subject: Fix for #159 --- docs/std/slice.md | 15 +++-- src/codegen/codegen_main.c | 33 +++++++++++ src/parser/parser_stmt.c | 114 ++++++++++++++++++++++++++++++++++++ src/parser/parser_struct.c | 115 +++++++++++++++++++++++++++++++++++++ std/mem.zc | 23 +------- std/slice.zc | 5 ++ tests/memory/test_memory_safety.zc | 9 ++- 7 files changed, 283 insertions(+), 31 deletions(-) (limited to 'tests') 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::from_array((int*)(&arr), 5); for val in slice { println "{val}"; @@ -39,6 +39,7 @@ struct Slice { | Method | Signature | Description | | :--- | :--- | :--- | | **from_array** | `Slice::from_array(arr: T*, len: usize) -> Slice` | Creates a slice view over an array. | +| **new** | `Slice::new(data: T*, len: usize) -> Slice` | Alias for `from_array` (backwards compat). | ### Iteration @@ -62,10 +63,10 @@ struct Slice { ### 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::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::from_array((int*)(&arr), 3); @@ -84,7 +87,7 @@ if (!opt.is_none()) { ## Notes -- `Slice` does not own its data - it's just a view +- `Slice` does not own its data — it's just a view - No memory management needed (no `free()` method) -- Must specify the generic type explicitly: `Slice`, `Slice`, 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_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_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, SliceIter, and Option 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 // 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/mem.zc b/std/mem.zc index 6ee96e8..f1a5f5a 100644 --- a/std/mem.zc +++ b/std/mem.zc @@ -49,28 +49,7 @@ impl Box { } } -struct Slice { - data: T*; - len: usize; -} - -impl Slice { - 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 is defined in std/slice.zc with iteration support fn mem_zero(ptr: T*, count: usize) { memset(ptr, 0, sizeof(T) * count); diff --git a/std/slice.zc b/std/slice.zc index 7ace396..3c317ca 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -32,6 +32,11 @@ impl Slice { return Slice { data: arr, len: len }; } + // Alias for backwards compatibility with std/mem.zc + fn new(data: T*, len: usize) -> Slice { + return Slice { data: data, len: len }; + } + fn iterator(self) -> SliceIter { return SliceIter { data: self.data, 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::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!"; -- cgit v1.2.3