From 70144b6829c3ca8ff8b46e40b358cc711db85efb Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Tue, 27 Jan 2026 22:11:50 +0000 Subject: Solving C++ interop bug --- src/codegen/codegen_stmt.c | 8 ++++---- src/repl/repl.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/codegen/codegen_stmt.c b/src/codegen/codegen_stmt.c index bd0c816..70fb297 100644 --- a/src/codegen/codegen_stmt.c +++ b/src/codegen/codegen_stmt.c @@ -884,7 +884,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " __auto_type %s = _tmp_%d.%s;\n", node->destruct.names[0], id, + fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[0], id, check); } } @@ -903,7 +903,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " __auto_type %s = _tmp_%d.%s;\n", node->destruct.names[i], + fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[i], id, field); } } @@ -916,7 +916,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " __auto_type %s = _tmp_%d.v%d;\n", node->destruct.names[i], + fprintf(out, " ZC_AUTO %s = _tmp_%d.v%d;\n", node->destruct.names[i], id, i); } } @@ -1528,7 +1528,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, "__auto_type"); + fprintf(out, "ZC_AUTO"); } fprintf(out, " _z_ret_mv = "); codegen_expression(ctx, node->ret.value, out); diff --git a/src/repl/repl.c b/src/repl/repl.c index cb63293..99d7b43 100644 --- a/src/repl/repl.c +++ b/src/repl/repl.c @@ -17,7 +17,7 @@ static int is_header_line(const char *line) void run_repl(const char *self_path) { - printf("\033[1;36mZen C REPL (v0.1)\033[0m\n"); + printf("\033[1;36mZen C REPL (%s)\033[0m\n", ZEN_VERSION); printf("Type 'exit' or 'quit' to leave.\n"); printf("Type :help for commands.\n"); -- cgit v1.2.3 From 7f7be8a8202b9ccea19ae379665e3a34a81b6797 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Tue, 27 Jan 2026 23:18:56 +0000 Subject: Improved examples in 'examples/' --- examples/data_structures/stack.zc | 8 ++--- examples/graphics/mandelbrot.zc | 74 +++++++++++++++++++------------------- examples/networking/echo_server.zc | 10 +++--- examples/tools/mini_grep.zc | 14 ++++---- src/codegen/codegen_utils.c | 4 +++ 5 files changed, 58 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/examples/data_structures/stack.zc b/examples/data_structures/stack.zc index 4a8593a..ea6a20a 100644 --- a/examples/data_structures/stack.zc +++ b/examples/data_structures/stack.zc @@ -1,11 +1,11 @@ import "std.zc" -struct Stack { +struct MyStack { data: Vec; } -impl Stack { +impl MyStack { fn new() -> Self { return Self { data: Vec::new() }; } @@ -43,7 +43,7 @@ impl Stack { fn main() { "[Integer Stack]"; - let int_stack = Stack::new(); + let int_stack = MyStack::new(); defer int_stack.free(); "Pushing: 10, 20, 30"; @@ -64,7 +64,7 @@ fn main() { ""; "[String Stack]"; - let str_stack = Stack::new(); + let str_stack = MyStack::new(); defer str_stack.free(); str_stack.push(String::new("First")); diff --git a/examples/graphics/mandelbrot.zc b/examples/graphics/mandelbrot.zc index 04f61b9..3a0662e 100644 --- a/examples/graphics/mandelbrot.zc +++ b/examples/graphics/mandelbrot.zc @@ -5,35 +5,37 @@ struct Complex { im: float, } -fn complex_make(re: float, im: float) -> Complex { - return Complex { re: re, im: im }; -} +impl Complex { + fn new(re: float, im: float) -> Complex { + return Complex { re: re, im: im }; + } -fn complex_add(a: Complex, b: Complex) -> Complex { - return Complex { re: a.re + b.re, im: a.im + b.im }; -} + fn add(self, b: Complex) -> Complex { + return Complex { re: self.re + b.re, im: self.im + b.im }; + } -fn complex_mul(a: Complex, b: Complex) -> Complex { - return Complex { - re: a.re * b.re - a.im * b.im, - im: a.re * b.im + a.im * b.re - }; -} + fn mul(self, b: Complex) -> Complex { + return Complex { + re: self.re * b.re - self.im * b.im, + im: self.re * b.im + self.im * b.re + }; + } -fn complex_abs2(z: Complex) -> float { - return z.re * z.re + z.im * z.im; -} + fn abs2(self) -> float { + return self.re * self.re + self.im * self.im; + } -fn complex_print(z: Complex) { - println "{z.re}{z.im}i"; + fn print(self) { + println "{self.re}{self.im}i"; + } } fn pick_char(iter: int, max_iter: int, edge_chars: char[], edge_count: int) -> char { if (iter >= max_iter) { return ' '; } if (iter <= 0) { return edge_chars[0]; } - let t: float = ((float)iter) / ((float)max_iter); - let idx: int = (int)(t * ((float)(edge_count - 1))); + let t = (float)iter / (float)max_iter; + let idx = (int)(t * (float)(edge_count - 1)); if (idx < 0) { idx = 0; } if (idx >= edge_count) { idx = edge_count - 1; } @@ -42,32 +44,30 @@ fn pick_char(iter: int, max_iter: int, edge_chars: char[], edge_count: int) -> c } fn main() { - let width: int = 120; - let height: int = 40; - let max_iter: int = 200; + let width = 120; + let height = 40; + let max_iter = 200; - let edge_chars: char[12] = [ '#', '@', '%', '8', '&', '*', '+', '=', '-', ':', '.', ',' ]; - let edge_count: int = 12; + let edge_chars: char[] = [ '#', '@', '%', '8', '&', '*', '+', '=', '-', ':', '.', ',' ]; + let edge_count = 12; - let min_re: float = -2.2; - let max_re: float = 1.0; - let min_im: float = -1.2; - let max_im: float = 1.2; + let min_re = -2.2; + let max_re = 1.0; + let min_im = -1.2; + let max_im = 1.2; for y in 0..height { - let im: float = - max_im - (max_im - min_im) * ( ((float)y) / ((float)(height - 1)) ); + let im = max_im - (max_im - min_im) * ((float)y / (float)(height - 1)); for x in 0..width { - let re: float = - min_re + (max_re - min_re) * ( ((float)x) / ((float)(width - 1)) ); + let re = min_re + (max_re - min_re) * ((float)x / (float)(width - 1)); - let c: Complex = complex_make(re, im); - let z: Complex = complex_make(0.0, 0.0); + let c = Complex::new(re, im); + let z = Complex::new(0.0, 0.0); - let iter: int = 0; - while (iter < max_iter and complex_abs2(z) <= 4.0) { - z = complex_add(complex_mul(z, z), c); + let iter = 0; + while (iter < max_iter and z.abs2() <= 4.0) { + z = z.mul(z).add(c); iter += 1; } diff --git a/examples/networking/echo_server.zc b/examples/networking/echo_server.zc index 072c3a2..2934923 100644 --- a/examples/networking/echo_server.zc +++ b/examples/networking/echo_server.zc @@ -1,11 +1,13 @@ import "std/net.zc" +def SIZE = 1024; + fn main() { "Starting Echo Server on 127.0.0.1:8080..."; let listener_res = TcpListener::bind("127.0.0.1", 8080); - if listener_res.is_err() { + guard listener_res.is_ok() else { !"Failed to bind: {listener_res.err}"; return 1; } @@ -19,9 +21,9 @@ fn main() { let stream = client_res.unwrap(); defer stream.close(); - let buf: char[1024]; - - let read_res = stream.read(buf, 1024); + let buf = (char*)malloc(SIZE); + defer free(buf); + let read_res = stream.read(buf, SIZE); if read_res.is_ok() { let bytes = read_res.unwrap(); diff --git a/examples/tools/mini_grep.zc b/examples/tools/mini_grep.zc index 39fec07..54338bf 100644 --- a/examples/tools/mini_grep.zc +++ b/examples/tools/mini_grep.zc @@ -30,7 +30,7 @@ fn str_find_case(haystack: string, needle: string, ignore_case: bool) -> Option< let n = needle; let n_len = strlen(needle); - while (*h != 0) + while (*h != '\0') { let is_match: bool = true; for i in 0..n_len @@ -52,7 +52,7 @@ fn str_find_case(haystack: string, needle: string, ignore_case: bool) -> Option< return Option::None(); } -fn print_highlight(line: string, match_ptr: string, match_len: usize, config: GrepConfig) { +fn print_highlight(line: string, match_ptr: string, match_len: usize, config: GrepConfig*) { if (!config.color) { "{line}"; @@ -84,7 +84,7 @@ fn print_highlight(line: string, match_ptr: string, match_len: usize, config: Gr "{current}"; } -fn grep_file(path: string, config: GrepConfig) -> Result { +fn grep_file(path: string, config: GrepConfig*) -> Result { let content_str: String = File::read_all(path)?; defer content_str.destroy(); let content = content_str.c_str(); @@ -95,7 +95,7 @@ fn grep_file(path: string, config: GrepConfig) -> Result { let q_len = strlen(config.query); - while (*ptr != 0) + while (*ptr != '\0') { if (*ptr == '\n') { @@ -163,7 +163,7 @@ fn grep_file(path: string, config: GrepConfig) -> Result { return Result::Ok(0); } -fn visit_dir(path: string, config: GrepConfig) { +fn visit_dir(path: string, config: GrepConfig*) { let entries_res = File::read_dir(path); guard entries_res.is_ok() else return; @@ -287,7 +287,7 @@ fn main(argc: int, argv: string*) { if (config.recursive) { - visit_dir(config.path, config); + visit_dir(config.path, &config); } else { @@ -296,7 +296,7 @@ fn main(argc: int, argv: string*) } else { - let res = grep_file(config.path, config); + let res = grep_file(config.path, &config); if (res.is_err()) { !"Error: {res.err}"; return 1; diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 8de3cf6..391ebd3 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -411,6 +411,10 @@ char *infer_type(ParserContext *ctx, ASTNode *node) char *inner = infer_type(ctx, node->unary.operand); if (inner) { + if (strcmp(inner, "string") == 0) + { + return xstrdup("char"); + } char *ptr = strchr(inner, '*'); if (ptr) { -- 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 'src') 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 'src') 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 4db1c9652e7f5d5476093604f0eadf230bd3aeec Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 01:45:25 +0000 Subject: Related to #139 --- src/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.c b/src/main.c index e2b3cde..b6392cc 100644 --- a/src/main.c +++ b/src/main.c @@ -362,8 +362,9 @@ int main(int argc, char **argv) // If using cosmocc, it handles these usually, but keeping them is okay for Linux targets snprintf(cmd, sizeof(cmd), "%s %s %s %s %s -o %s %s %s %s -I./src %s", g_config.cc, - g_config.gcc_flags, g_cflags, g_config.is_freestanding ? "-ffreestanding" : "", "", - outfile, temp_source_file, math_flag, thread_flag, g_link_flags); + g_config.gcc_flags, g_cflags, g_config.is_freestanding ? "-ffreestanding" : "", + g_config.quiet ? "-w" : "", outfile, temp_source_file, math_flag, thread_flag, + g_link_flags); if (g_config.verbose) { -- cgit v1.2.3 From 279bc13582cb681867bc4ebadb4287449a2c7383 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 10:31:05 +0000 Subject: Fix for #145 --- src/parser/parser_expr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 332d9c3..ee1f96f 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -3625,7 +3625,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } else if (t->kind == TYPE_STRING) { - strcat(fmt, "%s"); + strcat(fmt, "%ms"); } else if (t->kind == TYPE_CHAR || t->kind == TYPE_I8 || t->kind == TYPE_U8 || t->kind == TYPE_BYTE) -- 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 'src') 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 f8d9b233952357d327e856100835adf3cef47f23 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 15:58:55 +0000 Subject: Fix for #148 --- src/parser/parser_stmt.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'src') diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 85e9825..a471fe6 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -462,13 +462,7 @@ ASTNode *parse_defer(ParserContext *ctx, Lexer *l) } else { - s = ast_create(NODE_RAW_STMT); - char *raw_content = consume_and_rewrite(ctx, l); - // consume_and_rewrite strips the semicolon, so we must add it back for proper C generation - char *safe_content = xmalloc(strlen(raw_content) + 2); - sprintf(safe_content, "%s;", raw_content); - free(raw_content); - s->raw_stmt.content = safe_content; + s = parse_statement(ctx, l); } ctx->in_defer_block = prev_in_defer; -- 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 'src') 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 4049809f230e5e27adaf19aa57e7e9b69da5a4f0 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Thu, 29 Jan 2026 01:57:42 +0000 Subject: Improve REPL --- src/repl/repl.c | 1587 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 1333 insertions(+), 254 deletions(-) (limited to 'src') diff --git a/src/repl/repl.c b/src/repl/repl.c index 99d7b43..b04b2c2 100644 --- a/src/repl/repl.c +++ b/src/repl/repl.c @@ -6,13 +6,969 @@ #include #include #include +#include +#include +#include ASTNode *parse_program(ParserContext *ctx, Lexer *l); static int is_header_line(const char *line) { - return (strncmp(line, "import ", 7) == 0 || strncmp(line, "include ", 8) == 0 || - strncmp(line, "#include", 8) == 0); + // Skip whitespace + while (*line && (*line == ' ' || *line == '\t')) + { + line++; + } + if (strncmp(line, "struct", 6) == 0) + { + return 1; + } + if (strncmp(line, "impl", 4) == 0) + { + return 1; + } + if (strncmp(line, "fn", 2) == 0) + { + return 1; + } + if (strncmp(line, "use", 3) == 0) + { + return 1; + } + if (strncmp(line, "include", 7) == 0) + { + return 1; + } + if (strncmp(line, "typedef", 7) == 0) + { + return 1; + } + if (strncmp(line, "enum", 4) == 0) + { + return 1; + } + if (strncmp(line, "const", 5) == 0) + { + return 1; + } + if (strncmp(line, "def", 3) == 0) + { + return 1; + } + if (strncmp(line, "#include", 8) == 0) + { + return 1; + } + if (strncmp(line, "import", 6) == 0) + { + return 1; + } + + return 0; +} + +static void repl_error_callback(void *data, Token t, const char *msg) +{ + (void)data; + (void)t; + fprintf(stderr, "\033[1;31merror:\033[0m %s\n", msg); +} + +static struct termios orig_termios; +static int raw_mode_enabled = 0; + +static void disable_raw_mode() +{ + if (raw_mode_enabled) + { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); + raw_mode_enabled = 0; + } +} + +static void enable_raw_mode() +{ + if (!raw_mode_enabled) + { + tcgetattr(STDIN_FILENO, &orig_termios); + atexit(disable_raw_mode); + struct termios raw = orig_termios; + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); + raw_mode_enabled = 1; + } +} + +static const char *KEYWORDS[] = { + "fn", "struct", "var", "let", "def", "const", "return", "if", + "else", "for", "while", "do", "switch", "case", "default", "break", + "continue", "typedef", "enum", "union", "sizeof", "typeof", "import", "include", + "defer", "guard", "match", "impl", "trait", "comptime", "asm", "plugin", + "true", "false", "null", "NULL", NULL}; + +static const char *TYPES[] = {"void", "int", "char", "float", "double", "long", + "short", "unsigned", "signed", "bool", NULL}; + +static int find_matching_brace(const char *buf, int pos) +{ + if (pos < 0 || pos >= (int)strlen(buf)) + { + return -1; + } + char c = buf[pos]; + int dir = 0; + char match = 0; + if (c == '{') + { + match = '}'; + dir = 1; + } + else if (c == '(') + { + match = ')'; + dir = 1; + } + else if (c == '[') + { + match = ']'; + dir = 1; + } + else if (c == '}') + { + match = '{'; + dir = -1; + } + else if (c == ')') + { + match = '('; + dir = -1; + } + else if (c == ']') + { + match = '['; + dir = -1; + } + else + { + return -1; + } + + int depth = 1; + int p = pos + dir; + int len = strlen(buf); + while (p >= 0 && p < len) + { + if (buf[p] == c) + { + depth++; + } + else if (buf[p] == match) + { + depth--; + if (depth == 0) + { + return p; + } + } + p += dir; + } + return -1; +} + +// Calculate visible length of a string (ignoring ANSI codes) +static int get_visible_length(const char *str) +{ + int len = 0; + int in_esc = 0; + while (*str) + { + if (*str == '\033') + { + in_esc = 1; + } + else if (in_esc) + { + if (*str == 'm' || *str == 'K') // End of SGR or EL + { + in_esc = 0; + } + if (isalpha(*str)) + { + in_esc = 0; // Terminating char + } + } + else + { + len++; + } + str++; + } + return len; +} + +// Simple syntax highlighter for the REPL +static void repl_highlight(const char *buf, int cursor_pos); + +static int is_definition_of(const char *code, const char *name) +{ + Lexer l; + lexer_init(&l, code); + Token t = lexer_next(&l); + int is_header = 0; + + if (t.type == TOK_UNION) + { + is_header = 1; + } + else if (t.type == TOK_IDENT) + { + if ((t.len == 2 && strncmp(t.start, "fn", 2) == 0) || + (t.len == 6 && strncmp(t.start, "struct", 6) == 0) || + (t.len == 4 && strncmp(t.start, "enum", 4) == 0) || + (t.len == 7 && strncmp(t.start, "typedef", 7) == 0) || + (t.len == 5 && strncmp(t.start, "const", 5) == 0)) + { + is_header = 1; + } + } + + if (is_header) + { + Token name_tok = lexer_next(&l); + if (name_tok.type == TOK_IDENT) + { + if (strlen(name) == (size_t)name_tok.len && + strncmp(name, name_tok.start, name_tok.len) == 0) + { + return 1; + } + } + } + return 0; +} + +static void repl_highlight(const char *buf, int cursor_pos) +{ + const char *p = buf; + + int match_pos = -1; + int brace_pos = -1; + + // Check under cursor + if (find_matching_brace(buf, cursor_pos) != -1) + { + brace_pos = cursor_pos; + match_pos = find_matching_brace(buf, cursor_pos); + } + // Check before cursor (common behavior when typing) + else if (cursor_pos > 0 && find_matching_brace(buf, cursor_pos - 1) != -1) + { + brace_pos = cursor_pos - 1; + match_pos = find_matching_brace(buf, cursor_pos - 1); + } + + while (*p) + { + long idx = p - buf; + + // Highlight matching braces + if (idx == brace_pos || idx == match_pos) + { + printf("\033[1;44;37m"); // Bright White on Blue background + putchar(*p); + printf("\033[0m"); + p++; + continue; + } + + if (strncmp(p, "//", 2) == 0) + { + printf("\033[1;30m"); + printf("%s", p); + printf("\033[0m"); + break; + } + else if (*p == ':' && isalpha(p[1])) + { + printf("\033[1;35m"); + while (*p && !isspace(*p)) + { + putchar(*p); + p++; + } + printf("\033[0m"); + } + else if (isdigit(*p)) + { + printf("\033[1;35m"); + while (isdigit(*p) || *p == '.' || *p == 'x' || *p == 'X') + { + putchar(*p); + p++; + } + printf("\033[0m"); + } + else if (*p == '"' || *p == '\'') + { + char quote = *p; + printf("\033[1;32m"); + putchar(*p); + p++; + while (*p && *p != quote) + { + if (*p == '\\' && p[1]) + { + putchar(*p); + p++; + } + putchar(*p); + p++; + } + if (*p == quote) + { + putchar(*p); + p++; + } + printf("\033[0m"); + } + else if (strchr(",;.", *p)) + { + printf("\033[1;30m"); + putchar(*p); + printf("\033[0m"); + p++; + } + else if (strchr("{}[]()", *p)) + { + printf("\033[0;36m"); + putchar(*p); + printf("\033[0m"); + p++; + } + else if (strchr("+-*/=<>!&|^~%", *p)) + { + printf("\033[1;37m"); + putchar(*p); + printf("\033[0m"); + p++; + } + else if (isalpha(*p) || *p == '_') + { + const char *start = p; + while (isalnum(*p) || *p == '_') + { + p++; + } + int len = p - start; + char word[256]; + if (len < 256) + { + strncpy(word, start, len); + word[len] = 0; + + int is_keyword = 0; + for (int i = 0; KEYWORDS[i]; i++) + { + if (strcmp(word, KEYWORDS[i]) == 0) + { + is_keyword = 1; + break; + } + } + + int is_type = 0; + if (!is_keyword) + { + for (int i = 0; TYPES[i]; i++) + { + if (strcmp(word, TYPES[i]) == 0) + { + is_type = 1; + break; + } + } + } + + int is_func = 0; + if (!is_keyword && !is_type) + { + const char *peek = p; + while (*peek && isspace(*peek)) + { + peek++; + } + if (*peek == '(') + { + is_func = 1; + } + } + + int is_const = 0; + if (!is_keyword && !is_type && !is_func && len > 1) + { + int all_upper = 1; + int has_upper = 0; + for (int i = 0; word[i]; i++) + { + if (islower(word[i])) + { + all_upper = 0; + } + if (isupper(word[i])) + { + has_upper = 1; + } + } + if (all_upper && has_upper) + { + is_const = 1; + } + } + + if (is_keyword) + { + printf("\033[1;36m"); + } + else if (is_type) + { + printf("\033[1;33m"); + } + else if (is_func) + { + printf("\033[1;34m"); + } + else if (is_const) + { + printf("\033[1;31m"); + } + + printf("%s", word); + printf("\033[0m"); + } + else + { + printf("%.*s", len, start); + } + } + else + { + putchar(*p); + p++; + } + } +} + +static char *repl_complete(const char *buf, int pos) +{ + int start = pos; + while (start > 0 && (isalnum(buf[start - 1]) || buf[start - 1] == '_' || + buf[start - 1] == ':' || buf[start - 1] == '!')) + { + start--; + } + + int len = pos - start; + if (len == 0) + { + return NULL; + } + + char prefix[256]; + if (len >= 255) + { + return NULL; + } + strncpy(prefix, buf + start, len); + prefix[len] = 0; + + char *match = NULL; + int match_count = 0; + + for (int i = 0; KEYWORDS[i]; i++) + { + if (strncmp(KEYWORDS[i], prefix, len) == 0) + { + match = (char *)KEYWORDS[i]; + match_count++; + } + } + + static const char *COMMANDS[] = { + ":help", ":reset", ":imports", ":vars", ":funcs", ":structs", ":history", ":type", + ":time", ":c", ":doc", ":run", ":edit", ":save", ":load", ":watch", + ":unwatch", ":undo", ":delete", ":clear", ":quit", NULL}; + + if (prefix[0] == ':') + { + for (int i = 0; COMMANDS[i]; i++) + { + if (strncmp(COMMANDS[i], prefix, len) == 0) + { + match = (char *)COMMANDS[i]; + match_count++; + } + } + } + + if (match_count == 1) + { + return strdup(match + len); + } + + return NULL; +} + +static char *repl_readline(const char *prompt, char **history, int history_len, int indent_level) +{ + enable_raw_mode(); + + int buf_size = 1024; + char *buf = malloc(buf_size); + buf[0] = 0; + int len = 0; + int pos = 0; + + if (indent_level > 0) + { + for (int i = 0; i < indent_level * 4; i++) + { + if (len >= buf_size - 1) + { + buf_size *= 2; + buf = realloc(buf, buf_size); + } + buf[len++] = ' '; + } + buf[len] = 0; + pos = len; + } + + int history_idx = history_len; + char *saved_current_line = NULL; + + int in_search_mode = 0; + char search_buf[256]; + search_buf[0] = 0; + int search_match_idx = -1; + + printf("\r\033[K%s", prompt); + repl_highlight(buf, pos); + fflush(stdout); + + while (1) + { + char c; + if (read(STDIN_FILENO, &c, 1) != 1) + { + break; + } + + if (c == '\x1b') + { + char seq[3]; + if (read(STDIN_FILENO, &seq[0], 1) != 1) + { + continue; + } + if (read(STDIN_FILENO, &seq[1], 1) != 1) + { + continue; + } + + if (seq[0] == '[') + { + if (seq[1] == 'A') + { + if (history_idx > 0) + { + if (history_idx == history_len) + { + if (saved_current_line) + { + free(saved_current_line); + } + saved_current_line = strdup(buf); + } + history_idx--; + if (history_idx >= 0 && history_idx < history_len) + { + free(buf); + buf = strdup(history[history_idx]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + } + } + } + else if (seq[1] == 'B') + { + if (history_idx < history_len) + { + history_idx++; + free(buf); + if (history_idx == history_len) + { + if (saved_current_line) + { + buf = strdup(saved_current_line); + } + else + { + buf = strdup(""); + } + } + else + { + buf = strdup(history[history_idx]); + } + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + } + } + else if (seq[1] == 'C') + { + if (pos < len) + { + pos++; + } + } + else if (seq[1] == 'D') + { + if (pos > 0) + { + pos--; + } + } + else if (seq[1] == 'H') + { + pos = 0; + } + else if (seq[1] == 'F') + { + pos = len; + } + } + } + else if (c == 127 || c == 8) + { + if (pos > 0) + { + memmove(buf + pos - 1, buf + pos, len - pos + 1); + len--; + pos--; + } + } + else if (c == '\r' || c == '\n') + { + printf("\r\n"); + break; + } + else if (c == 3) + { + printf("^C\r\n"); + free(buf); + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + return strdup(""); + } + else if (c == 4) + { + if (len == 0) + { + free(buf); + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + return NULL; + } + } + else if (c == '\t') + { + char *completion = repl_complete(buf, pos); + if (completion) + { + int clen = strlen(completion); + if (len + clen < buf_size - 1) + { + // Insert completion + memmove(buf + pos + clen, buf + pos, len - pos + 1); + memcpy(buf + pos, completion, clen); + len += clen; + pos += clen; + } + free(completion); + } + } + else if (c == 18) + { + if (!in_search_mode) + { + in_search_mode = 1; + search_buf[0] = 0; + search_match_idx = history_len; + } + + int found = -1; + int start_idx = search_match_idx - 1; + if (start_idx >= history_len) + { + start_idx = history_len - 1; + } + + for (int i = start_idx; i >= 0; i--) + { + if (strstr(history[i], search_buf)) + { + found = i; + break; + } + } + + if (found != -1) + { + search_match_idx = found; + free(buf); + buf = strdup(history[found]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + history_idx = found; // Sync history navigation + } + } + else if (in_search_mode) + { + if (c == 127 || c == 8) // Backspace + { + int sl = strlen(search_buf); + if (sl > 0) + { + search_buf[sl - 1] = 0; + search_match_idx = history_len; + int found = -1; + for (int i = history_len - 1; i >= 0; i--) + { + if (strstr(history[i], search_buf)) + { + found = i; + break; + } + } + if (found != -1) + { + search_match_idx = found; + free(buf); + buf = strdup(history[found]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + history_idx = found; + } + } + } + else if (c == '\r' || c == '\n' || c == 27 || c == 7 || + c == 3) // Enter/Esc/Ctrl+G/Ctrl+C + { + in_search_mode = 0; + if (c == 3) + { + // Abort + free(buf); + buf = strdup(""); + len = 0; + pos = 0; + printf("^C\r\n"); + return buf; + } + if (c == 7) + { + // Keep current match + } + else if (c == '\r' || c == '\n') + { + printf("\r\n"); + break; + } + } + else if (!iscntrl(c)) + { + int sl = strlen(search_buf); + if (sl < 255) + { + search_buf[sl] = c; + search_buf[sl + 1] = 0; + + int found = -1; + for (int i = history_len - 1; i >= 0; i--) + { + if (strstr(history[i], search_buf)) + { + found = i; + break; + } + } + if (found != -1) + { + search_match_idx = found; + free(buf); + buf = strdup(history[found]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + history_idx = found; + } + } + } + } + else if (c == 1) + { + pos = 0; + } + else if (c == 5) + { + pos = len; + } + else if (c == 12) + { + printf("\033[2J\033[H"); + } + else if (c == 21) + { + if (pos > 0) + { + memmove(buf, buf + pos, len - pos + 1); + len -= pos; + pos = 0; + } + } + else if (c == 11) + { + buf[pos] = 0; + len = pos; + } + else if (c == 14) + { + printf("^N\r\n"); + free(buf); + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + return strdup(":reset"); + } + else if (!iscntrl(c)) + { + if (len >= buf_size - 1) + { + buf_size *= 2; + buf = realloc(buf, buf_size); + } + memmove(buf + pos + 1, buf + pos, len - pos + 1); + buf[pos] = c; + len++; + pos++; + } + + if (in_search_mode) + { + printf("\r\033[K(reverse-i-search)`%s': %s", search_buf, buf); + } + else + { + printf("\r\033[K%s", prompt); + repl_highlight(buf, pos); + int prompt_len = get_visible_length(prompt); + if (pos + prompt_len > 0) + { + printf("\r\033[%dC", pos + prompt_len); + } + else + { + printf("\r"); + } + } + + fflush(stdout); + } + + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + + return buf; +} + +static void repl_get_code(char **history, int len, char **out_global, char **out_main) +{ + size_t total_len = 0; + for (int i = 0; i < len; i++) + { + total_len += strlen(history[i]) + 2; + } + + char *global_buf = malloc(total_len + 1); + char *main_buf = malloc(total_len + 1); + global_buf[0] = 0; + main_buf[0] = 0; + + int brace_depth = 0; + int in_global = 0; + + for (int i = 0; i < len; i++) + { + char *line = history[i]; + + if (brace_depth == 0) + { + if (is_header_line(line)) + { + in_global = 1; + } + else + { + in_global = 0; + } + } + + if (in_global) + { + strcat(global_buf, line); + strcat(global_buf, "\n"); + } + else + { + strcat(main_buf, line); + strcat(main_buf, " "); + } + + for (char *p = line; *p; p++) + { + if (*p == '{') + { + brace_depth++; + } + else if (*p == '}') + { + brace_depth--; + } + } + } + + *out_global = global_buf; + *out_main = main_buf; } void run_repl(const char *self_path) @@ -21,7 +977,6 @@ void run_repl(const char *self_path) printf("Type 'exit' or 'quit' to leave.\n"); printf("Type :help for commands.\n"); - // Dynamic history. int history_cap = 64; int history_len = 0; char **history = xmalloc(history_cap * sizeof(char *)); @@ -69,7 +1024,6 @@ void run_repl(const char *self_path) history_path[0] = 0; } - // Watch list. char *watches[16]; int watches_len = 0; for (int i = 0; i < 16; i++) @@ -77,7 +1031,6 @@ void run_repl(const char *self_path) watches[i] = NULL; } - // Load startup file (~/.zprep_init.zc) if exists if (home) { char init_path[512]; @@ -128,21 +1081,39 @@ void run_repl(const char *self_path) while (1) { - if (brace_depth > 0 || paren_depth > 0) + char cwd[1024]; + char prompt_text[1280]; + if (getcwd(cwd, sizeof(cwd))) { - printf("... "); + char *base = strrchr(cwd, '/'); + if (base) + { + base++; + } + else + { + base = cwd; + } + snprintf(prompt_text, sizeof(prompt_text), "\033[1;32m%s >>>\033[0m ", base); } else { - printf("\033[1;32m>>>\033[0m "); + strcpy(prompt_text, "\033[1;32m>>>\033[0m "); } - if (!fgets(line_buf, sizeof(line_buf), stdin)) + const char *prompt = (brace_depth > 0 || paren_depth > 0) ? "... " : prompt_text; + int indent = (brace_depth > 0) ? brace_depth : 0; + char *rline = repl_readline(prompt, history, history_len, indent); + + if (!rline) { break; } + strncpy(line_buf, rline, sizeof(line_buf) - 2); + line_buf[sizeof(line_buf) - 2] = 0; + strcat(line_buf, "\n"); + free(rline); - // Handle commands (only on fresh line). if (NULL == input_buffer) { size_t len = strlen(line_buf); @@ -162,8 +1133,13 @@ void run_repl(const char *self_path) break; } - // Commands - if (cmd_buf[0] == ':' || cmd_buf[0] == '!') + if (cmd_buf[0] == '!') + { + int ret = system(cmd_buf + 1); + printf("(exit code: %d)\n", ret); + continue; + } + if (cmd_buf[0] == ':') { if (0 == strcmp(cmd_buf, ":help")) { @@ -183,14 +1159,21 @@ void run_repl(const char *self_path) printf(" :edit [n] Edit command n (default: last) in $EDITOR\n"); printf(" :save Save session to file\n"); printf(" :load Load file into session\n"); - printf(" :load Load file into session\n"); - printf(" :watch Watch expression output\n"); - printf(" :unwatch Remove watch n\n"); printf(" :undo Remove last command\n"); printf(" :delete Remove command at index n\n"); + printf(" :watch Watch expression output\n"); + printf(" :unwatch Remove watch n\n"); printf(" :clear Clear screen\n"); printf(" ! Run shell command\n"); printf(" :quit Exit REPL\n"); + printf("\nShortcuts:\n"); + printf(" Up/Down History navigation\n"); + printf(" Tab Completion\n"); + printf(" Ctrl+A Go to start\n"); + printf(" Ctrl+E Go to end\n"); + printf(" Ctrl+L Clear screen\n"); + printf(" Ctrl+U Clear line to start\n"); + printf(" Ctrl+K Clear line to end\n"); continue; } else if (0 == strcmp(cmd_buf, ":reset")) @@ -207,9 +1190,107 @@ void run_repl(const char *self_path) { break; } + else if (0 == strncmp(cmd_buf, ":show ", 6)) + { + char *name = cmd_buf + 6; + while (*name && isspace(*name)) + { + name++; + } + + int found = 0; + printf("Source definition for '%s':\n", name); + + for (int i = history_len - 1; i >= 0; i--) + { + if (is_definition_of(history[i], name)) + { + printf(" \033[90m// Found in history:\033[0m\n"); + printf(" "); + repl_highlight(history[i], -1); + printf("\n"); + found = 1; + break; + } + } + + if (found) + { + continue; + } + + printf("Source definition for '%s':\n", name); + + size_t show_code_size = 4096; + for (int i = 0; i < history_len; i++) + { + show_code_size += strlen(history[i]) + 2; + } + char *show_code = malloc(show_code_size); + strcpy(show_code, ""); + for (int i = 0; i < history_len; i++) + { + strcat(show_code, history[i]); + strcat(show_code, "\n"); + } + + ParserContext ctx = {0}; + ctx.is_repl = 1; + ctx.skip_preamble = 1; + ctx.is_fault_tolerant = 1; + ctx.on_error = repl_error_callback; + Lexer l; + lexer_init(&l, show_code); + ASTNode *nodes = parse_program(&ctx, &l); + + ASTNode *search = nodes; + if (search && search->type == NODE_ROOT) + { + search = search->root.children; + } + + for (ASTNode *n = search; n; n = n->next) + { + if (n->type == NODE_FUNCTION && 0 == strcmp(n->func.name, name)) + { + printf(" fn %s(%s) -> %s\n", n->func.name, + n->func.args ? n->func.args : "", + n->func.ret_type ? n->func.ret_type : "void"); + found = 1; + break; + } + else if (n->type == NODE_STRUCT && 0 == strcmp(n->strct.name, name)) + { + printf(" struct %s {\n", n->strct.name); + for (ASTNode *field = n->strct.fields; field; field = field->next) + { + if (field->type == NODE_FIELD) + { + printf(" %s: %s;\n", field->field.name, field->field.type); + } + else if (field->type == NODE_VAR_DECL) + { + // Fields might be VAR_DECLs in some parses? No, usually + // NODE_FIELD for structs. + printf(" %s: %s;\n", field->var_decl.name, + field->var_decl.type_str); + } + } + printf(" }\n"); + found = 1; + break; + } + } + if (!found) + { + printf(" (not found)\n"); + } + free(show_code); + continue; + } else if (0 == strcmp(cmd_buf, ":clear")) { - printf("\033[2J\033[H"); // ANSI clear screen + printf("\033[2J\033[H"); continue; } else if (0 == strcmp(cmd_buf, ":undo")) @@ -289,9 +1370,7 @@ void run_repl(const char *self_path) const char *editor = getenv("EDITOR"); if (!editor) { - editor = "nano"; // Default fallback, - // 'cause I know some of you - // don't know how to exit Vim. + editor = "nano"; } char cmd[1024]; @@ -300,7 +1379,6 @@ void run_repl(const char *self_path) if (0 == status) { - // Read back file. FILE *fr = fopen(edit_path, "r"); if (fr) { @@ -415,23 +1493,15 @@ void run_repl(const char *self_path) FILE *f = fopen(filename, "w"); if (f) { - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - fprintf(f, "%s\n", history[i]); - } - } - // Write main function body. - fprintf(f, "\nfn main() {\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - fprintf(f, " %s\n", history[i]); - } - } - fprintf(f, "}\n"); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + fprintf(f, "%s\n", global_code); + fprintf(f, "\nfn main() {\n%s\n}\n", main_code); + + free(global_code); + free(main_code); fclose(f); printf("Session saved to %s\n", filename); } @@ -501,36 +1571,21 @@ void run_repl(const char *self_path) else if (0 == strcmp(cmd_buf, ":vars") || 0 == strcmp(cmd_buf, ":funcs") || 0 == strcmp(cmd_buf, ":structs")) { - size_t code_size = 4096; - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } - char *code = malloc(code_size + 128); - strcpy(code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "fn main() { "); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, " "); - } - } - strcat(code, " }"); + size_t code_size = strlen(global_code) + strlen(main_code) + 128; + char *code = malloc(code_size); + sprintf(code, "%s\nfn main() { %s }", global_code, main_code); + free(global_code); + free(main_code); ParserContext ctx = {0}; ctx.is_repl = 1; ctx.skip_preamble = 1; + ctx.is_fault_tolerant = 1; + ctx.on_error = repl_error_callback; Lexer l; lexer_init(&l, code); @@ -553,11 +1608,27 @@ void run_repl(const char *self_path) break; } } - printf("Variables:\n"); + + // Generate probe code to print values + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + // Generate probe code to print values + size_t probe_size = strlen(global_code) + strlen(main_code) + 4096; + char *probe_code = malloc(probe_size); + + sprintf(probe_code, + "%s\nfn main() { _z_suppress_stdout(); %s _z_restore_stdout(); " + "printf(\"Variables:\\n\"); ", + global_code, main_code); + free(global_code); + free(main_code); + + int found_vars = 0; if (main_func && main_func->func.body && main_func->func.body->type == NODE_BLOCK) { - int found = 0; for (ASTNode *s = main_func->func.body->block.statements; s; s = s->next) { @@ -565,19 +1636,92 @@ void run_repl(const char *self_path) { char *t = s->var_decl.type_str ? s->var_decl.type_str : "Inferred"; - printf(" %s: %s\n", s->var_decl.name, t); - found = 1; + // Heuristic for format + char fmt[64]; + char val_expr[128]; + + if (s->var_decl.type_str) + { + if (strcmp(t, "int") == 0 || strcmp(t, "i32") == 0) + { + strcpy(fmt, "%d"); + strcpy(val_expr, s->var_decl.name); + } + else if (strcmp(t, "i64") == 0) + { + strcpy(fmt, "%ld"); + sprintf(val_expr, "(long)%s", s->var_decl.name); + } + else if (strcmp(t, "float") == 0 || + strcmp(t, "double") == 0 || + strcmp(t, "f32") == 0 || strcmp(t, "f64") == 0) + { + strcpy(fmt, "%f"); + strcpy(val_expr, s->var_decl.name); + } + else if (strcmp(t, "bool") == 0) + { + strcpy(fmt, "%s"); + sprintf(val_expr, "%s ? \"true\" : \"false\"", + s->var_decl.name); + } + else if (strcmp(t, "string") == 0 || + strcmp(t, "char*") == 0) + { + strcpy(fmt, "\\\"%s\\\""); + strcpy(val_expr, s->var_decl.name); + } // quote strings + else if (strcmp(t, "char") == 0) + { + strcpy(fmt, "'%c'"); + strcpy(val_expr, s->var_decl.name); + } + else + { + // Fallback: address + strcpy(fmt, "@%p"); + sprintf(val_expr, "(void*)&%s", s->var_decl.name); + } + } + else + { + // Inferred: Safe fallback? Or try to guess? + // For now, minimal safety: print address + strcpy(fmt, "? @%p"); + sprintf(val_expr, "(void*)&%s", s->var_decl.name); + } + + char print_stmt[512]; + snprintf(print_stmt, sizeof(print_stmt), + "printf(\" %s (%s): %s\\n\", %s); ", s->var_decl.name, + t, fmt, val_expr); + strcat(probe_code, print_stmt); + found_vars = 1; } } - if (!found) - { - printf(" (none)\n"); - } } - else + + if (!found_vars) { - printf(" (none)\n"); + strcat(probe_code, "printf(\" (none)\\n\");"); + } + + strcat(probe_code, " }"); + + // Execute + char tmp_path[256]; + snprintf(tmp_path, sizeof(tmp_path), "/tmp/zen_repl_vars_%d.zc", getpid()); + FILE *f = fopen(tmp_path, "w"); + if (f) + { + fprintf(f, "%s", probe_code); + fclose(f); + char cmd[512]; + snprintf(cmd, sizeof(cmd), "%s run -q %s", self_path, tmp_path); + system(cmd); + remove(tmp_path); } + free(probe_code); } else if (0 == strcmp(cmd_buf, ":funcs")) { @@ -621,36 +1765,21 @@ void run_repl(const char *self_path) { char *expr = cmd_buf + 6; - size_t probe_size = 4096; - for (int i = 0; i < history_len; i++) - { - probe_size += strlen(history[i]) + 2; - } - - char *probe_code = malloc(probe_size + strlen(expr) + 256); - strcpy(probe_code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, "\n"); - } - } + size_t probe_size = + strlen(global_code) + strlen(main_code) + strlen(expr) + 4096; + char *probe_code = malloc(probe_size); - strcat(probe_code, "fn main() { _z_suppress_stdout(); "); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, " "); - } - } + sprintf(probe_code, "%s\nfn main() { _z_suppress_stdout(); %s", global_code, + main_code); + free(global_code); + free(main_code); strcat(probe_code, " raw { typedef struct { int _u; } __REVEAL_TYPE__; } "); - strcat(probe_code, " var _z_type_probe: __REVEAL_TYPE__; _z_type_probe = ("); + strcat(probe_code, " let _z_type_probe: __REVEAL_TYPE__; _z_type_probe = ("); strcat(probe_code, expr); strcat(probe_code, "); }"); @@ -686,18 +1815,51 @@ void run_repl(const char *self_path) int found = 0; while (fgets(buf, sizeof(buf), p)) { - char *marker = "right operand has type '"; - char *start = strstr(buf, marker); + char *start = strstr(buf, "from type "); + char quote = 0; + if (!start) + { + start = strstr(buf, "incompatible type "); + } + if (start) { - start += strlen(marker); - char *end = strchr(start, '\''); - if (end) + char *q = strchr(start, '\''); + if (!q) { - *end = 0; - printf("\033[1;36mType: %s\033[0m\n", start); - found = 1; - break; + q = strstr(start, "\xe2\x80\x98"); + } + + if (q) + { + if (*q == '\'') + { + start = q + 1; + quote = '\''; + } + else + { + start = q + 3; + quote = 0; + } + + char *end = NULL; + if (quote) + { + end = strchr(start, quote); + } + else + { + end = strstr(start, "\xe2\x80\x99"); + } + + if (end) + { + *end = 0; + printf("\033[1;36mType: %s\033[0m\n", start); + found = 1; + break; + } } } } @@ -716,33 +1878,21 @@ void run_repl(const char *self_path) // Benchmark an expression. char *expr = cmd_buf + 6; - size_t code_size = 4096; - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } - char *code = malloc(code_size + strlen(expr) + 256); - strcpy(code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + size_t code_size = + strlen(global_code) + strlen(main_code) + strlen(expr) + 4096; + char *code = malloc(code_size); + + sprintf(code, + "%s\ninclude \"time.h\"\nfn main() { _z_suppress_stdout();\n%s " + "_z_restore_stdout();\n", + global_code, main_code); + free(global_code); + free(main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "include \"time.h\"\n"); - strcat(code, "fn main() { _z_suppress_stdout();\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, " "); - } - } - strcat(code, "_z_restore_stdout();\n"); strcat(code, "raw { clock_t _start = clock(); }\n"); strcat(code, "for _i in 0..1000 { "); strcat(code, expr); @@ -800,17 +1950,11 @@ void run_repl(const char *self_path) while (brace_depth > 0) { - printf("... "); - char more[1024]; - if (!fgets(more, sizeof(more), stdin)) + char *more = repl_readline("... ", history, history_len, brace_depth); + if (!more) { break; } - size_t mlen = strlen(more); - if (mlen > 0 && more[mlen - 1] == '\n') - { - more[--mlen] = 0; - } strcat(expr_buf, "\n"); strcat(expr_buf, more); for (char *p = more; *p; p++) @@ -824,35 +1968,20 @@ void run_repl(const char *self_path) brace_depth--; } } + free(more); } - size_t code_size = 4096 + strlen(expr_buf); - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } - char *code = malloc(code_size + 128); - strcpy(code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "fn main() {\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, " "); - } - } - strcat(code, expr_buf); - strcat(code, "\n}"); + size_t code_size = + strlen(global_code) + strlen(main_code) + strlen(expr_buf) + 128; + char *code = malloc(code_size); + + sprintf(code, "%s\nfn main() { %s %s }", global_code, main_code, expr_buf); + free(global_code); + free(main_code); free(expr_buf); char tmp_path[256]; @@ -877,33 +2006,16 @@ void run_repl(const char *self_path) } else if (0 == strcmp(cmd_buf, ":run")) { - size_t code_size = 4096; - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + size_t code_size = strlen(global_code) + strlen(main_code) + 128; char *code = malloc(code_size); - strcpy(code, ""); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "fn main() {\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, " "); - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "}\n"); + sprintf(code, "%s\nfn main() { %s }", global_code, main_code); + free(global_code); + free(main_code); char tmp_path[256]; sprintf(tmp_path, "/tmp/zprep_repl_run_%d.zc", rand()); @@ -1082,7 +2194,6 @@ void run_repl(const char *self_path) } if (!found) { - // Fallback: try man pages, show only SYNOPSIS. char man_cmd[256]; sprintf(man_cmd, "man 3 %s 2>/dev/null | sed -n '/^SYNOPSIS/,/^[A-Z]/p' | " @@ -1187,7 +2298,6 @@ void run_repl(const char *self_path) continue; } - // Add to history. if (history_len >= history_cap) { history_cap *= 2; @@ -1201,40 +2311,20 @@ void run_repl(const char *self_path) brace_depth = 0; paren_depth = 0; - size_t total_size = 4096; - for (int i = 0; i < history_len; i++) - { - total_size += strlen(history[i]) + 2; - } + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + size_t total_size = strlen(global_code) + strlen(main_code) + 4096; if (watches_len > 0) { - total_size += 16 * 1024; // Plenty of space for watches. Yeah static ik. + total_size += 16 * 1024; } char *full_code = malloc(total_size); - strcpy(full_code, ""); - - // Hoisting pass. - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(full_code, history[i]); - strcat(full_code, "\n"); - } - } - - strcat(full_code, "fn main() { _z_suppress_stdout(); "); - - for (int i = 0; i < history_len - 1; i++) - { - if (is_header_line(history[i])) - { - continue; - } - strcat(full_code, history[i]); - strcat(full_code, " "); - } + sprintf(full_code, "%s\nfn main() { _z_suppress_stdout(); %s", global_code, main_code); + free(global_code); + free(main_code); strcat(full_code, "_z_restore_stdout(); "); @@ -1249,6 +2339,8 @@ void run_repl(const char *self_path) ParserContext ctx = {0}; ctx.is_repl = 1; ctx.skip_preamble = 1; + ctx.is_fault_tolerant = 1; + ctx.on_error = repl_error_callback; Lexer l; lexer_init(&l, check_buf); ASTNode *node = parse_statement(&ctx, &l); @@ -1272,33 +2364,17 @@ void run_repl(const char *self_path) if (is_expr) { - size_t probesz = 4096; - for (int i = 0; i < history_len - 1; i++) - { - probesz += strlen(history[i]) + 2; - } - char *probe_code = malloc(probesz + strlen(last_line) + 512); - strcpy(probe_code, ""); - - for (int i = 0; i < history_len - 1; i++) - { - if (is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, "\n"); - } - } + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len - 1, &global_code, &main_code); - strcat(probe_code, "fn main() { _z_suppress_stdout(); "); + size_t probesz = strlen(global_code) + strlen(main_code) + strlen(last_line) + 4096; + char *probe_code = malloc(probesz); - for (int i = 0; i < history_len - 1; i++) - { - if (!is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, " "); - } - } + sprintf(probe_code, "%s\nfn main() { _z_suppress_stdout(); %s", global_code, + main_code); + free(global_code); + free(main_code); strcat(probe_code, " raw { typedef struct { int _u; } __REVEAL_TYPE__; } "); strcat(probe_code, " var _z_type_probe: __REVEAL_TYPE__; _z_type_probe = ("); @@ -1356,10 +2432,9 @@ void run_repl(const char *self_path) if (watches_len > 0) { - strcat(full_code, "; "); // separator. + strcat(full_code, "; "); for (int i = 0; i < watches_len; i++) { - // Use printf for label, then print "{expr}" for value. char wbuf[1024]; sprintf(wbuf, "printf(\"\\033[90mwatch:%s = \\033[0m\"); print \"{%s}\"; " @@ -1401,7 +2476,8 @@ void run_repl(const char *self_path) FILE *hf = fopen(history_path, "w"); if (hf) { - for (int i = 0; i < history_len; i++) + int start = history_len > 1000 ? history_len - 1000 : 0; + for (int i = start; i < history_len; i++) { fprintf(hf, "%s\n", history[i]); } @@ -1409,11 +2485,14 @@ void run_repl(const char *self_path) } } - for (int i = 0; i < history_len; i++) + if (history) { - free(history[i]); + for (int i = 0; i < history_len; i++) + { + free(history[i]); + } + free(history); } - free(history); if (input_buffer) { free(input_buffer); -- cgit v1.2.3 From da9e8758e9d89dc7362be67f8e7573309efe170c Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Thu, 29 Jan 2026 03:03:56 +0000 Subject: Centralize TCC compat --- src/codegen/codegen_decl.c | 5 +++-- src/codegen/compat.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 5fb9f54..11cdece 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -3,6 +3,7 @@ #include "../parser/parser.h" #include "../zprep.h" #include "codegen.h" +#include "compat.h" #include #include #include @@ -12,7 +13,7 @@ static void emit_freestanding_preamble(FILE *out) fputs("#include \n#include \n#include " "\n#include \n", out); - fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out); + fputs(ZC_TCC_COMPAT_STR, out); fputs("typedef size_t usize;\ntypedef char* string;\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", @@ -86,7 +87,7 @@ void emit_preamble(ParserContext *ctx, FILE *out) // C mode fputs("#define ZC_AUTO __auto_type\n", out); fputs("#define ZC_CAST(T, x) ((T)(x))\n", out); - fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out); + fputs(ZC_TCC_COMPAT_STR, out); fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : " "\"false\"; }\n", out); diff --git a/src/codegen/compat.h b/src/codegen/compat.h index f2d221a..26b0df5 100644 --- a/src/codegen/compat.h +++ b/src/codegen/compat.h @@ -22,6 +22,37 @@ #define ZC_EXTERN_C_END #endif +#ifdef __TINYC__ +/* TCC compatibility */ +#ifndef __auto_type +#define __auto_type __typeof__ +#endif + +#ifndef __builtin_expect +#define __builtin_expect(x, v) (x) +#endif + +#ifndef __builtin_unreachable +#define __builtin_unreachable() +#endif +#endif + +/* Centralized string definition for codegen emission */ +#define ZC_TCC_COMPAT_STR \ + "#ifdef __TINYC__\n" \ + "#ifndef __auto_type\n" \ + "#define __auto_type __typeof__\n" \ + "#endif\n" \ + "\n" \ + "#ifndef __builtin_expect\n" \ + "#define __builtin_expect(x, v) (x)\n" \ + "#endif\n" \ + "\n" \ + "#ifndef __builtin_unreachable\n" \ + "#define __builtin_unreachable()\n" \ + "#endif\n" \ + "#endif\n" + #ifdef __cplusplus #include -- 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 'src') 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 ee090168fd7f678e40150b3699e335625b90d947 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Thu, 29 Jan 2026 22:45:43 +0000 Subject: Fix for #150 --- src/parser/parser_expr.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src') diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 8f3579a..51d2baa 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -156,6 +156,14 @@ int is_type_copy(ParserContext *ctx, Type *t) { return 1; } + + // If the struct is NOT defined (opaque/C type) and does NOT implement Drop, + // treat it as Copy (C behavior). + if (!find_struct_def(ctx, t->name) && !check_impl(ctx, "Drop", t->name)) + { + return 1; + } + return 0; case TYPE_ARRAY: @@ -164,6 +172,13 @@ int is_type_copy(ParserContext *ctx, Type *t) // but if it's a value assignment, C doesn't support it anyway unless wrapped in struct. return 0; + case TYPE_ALIAS: + if (t->alias.is_opaque_alias) + { + return 1; + } + return is_type_copy(ctx, t->inner); + default: return 1; } -- cgit v1.2.3 From 0427d254207a69e394499d1abaea768f484f1cb5 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 02:24:10 +0000 Subject: Improvements related to C23 (#112) --- README.md | 12 +++++++ README_ES.md | 11 ++++++ README_ZH_CN.md | 11 ++++++ README_ZH_TW.md | 11 ++++++ src/ast/ast.c | 25 ++++++++++++++ src/ast/ast.h | 4 ++- src/codegen/codegen_decl.c | 4 +++ src/codegen/compat.h | 6 +++- src/parser/parser_expr.c | 2 ++ src/parser/parser_type.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 168 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 75c4624..f03f605 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [C++ Interop](#c-interop) - [CUDA Interop](#cuda-interop) - [Objective-C Interop](#objective-c-interop) + - [C23 Support](#c23-support) - [Contributing](#contributing) - [Attributions](#attributions) @@ -197,6 +198,8 @@ let y: const int = 10; // Read-only (Type qualified) // y = 20; // Error: cannot assign to const ``` +> **Type Inference**: Zen C automatically infers types for initialized variables. It compiles to C23 `auto` on supported compilers, or GCC's `__auto_type` extension otherwise. + ### 2. Primitive Types | Type | C Equivalent | Description | @@ -211,6 +214,8 @@ let y: const int = 10; // Read-only (Type qualified) | `char` | `char` | Single character | | `string` | `char*` | C-string (null-terminated) | | `U0`, `u0`, `void` | `void` | Empty type | +| `iN` (for example, `i256`) | `_BitInt(N)` | Arbitrary bit-width signed integer (C23) | +| `uN` (for example, `u42`) | `unsigned _BitInt(N)` | Arbitrary bit-width unsigned integer (C23) | ### 3. Aggregate Types @@ -1337,6 +1342,13 @@ let tid = local_id(); > **Note:** The `--cuda` flag sets `nvcc` as the compiler and implies `--cpp` mode. Requires the NVIDIA CUDA Toolkit. +### C23 Support + +Zen C supports modern C23 features when using a compatible backend compiler (GCC 14+, Clang 14+, TCC (partial)). + +- **`auto`**: Zen C automatically maps type inference to standard C23 `auto` if `__STDC_VERSION__ >= 202300L`. +- **`_BitInt(N)`**: Use `iN` and `uN` types (e.g., `i256`, `u12`, `i24`) to access C23 arbitrary-width integers. + ### 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. diff --git a/README_ES.md b/README_ES.md index c5655e3..9040ea8 100644 --- a/README_ES.md +++ b/README_ES.md @@ -197,6 +197,8 @@ let y: const int = 10; // Solo lectura (Calificado por tipo) // y = 20; // Error: no se puede asignar a una constante ``` +> **Inferencia de tipos**: Zen C infiere automáticamente los tipos para variables inicializadas. Se compila a `auto` de C23 en compiladores compatibles, o a la extensión `__auto_type` de GCC en otros casos. + ### 2. Tipos Primitivos | Tipo | Equivalente en C | Descripción | @@ -211,6 +213,8 @@ let y: const int = 10; // Solo lectura (Calificado por tipo) | `char` | `char` | Carácter único | | `string` | `char*` | Cadena de C (terminada en null) | | `U0`, `u0`, `void` | `void` | Tipo vacío | +| `iN` (ej. `i256`) | `_BitInt(N)` | Entero con signo de ancho arbitrario (C23) | +| `uN` (ej. `u42`) | `unsigned _BitInt(N)` | Entero sin signo de ancho arbitrario (C23) | ### 3. Tipos Agregados @@ -1337,6 +1341,13 @@ let tid = local_id(); > **Nota:** La flag `--cuda` establece `nvcc` como el compilador e implica el modo `--cpp`. Requiere el NVIDIA CUDA Toolkit. +### Soporte C23 + +Zen C soporta características modernas de C23 cuando se utiliza un compilador backend compatible (GCC 14+, Clang 14+). + +- **`auto`**: Zen C mapea automáticamente la inferencia de tipos a `auto` estándar de C23 si `__STDC_VERSION__ >= 202300L`. +- **`_BitInt(N)`**: Use tipos `iN` y `uN` (ej. `i256`, `u12`, `i24`) para acceder a enteros de ancho arbitrario de C23. + ### Interop con Objective-C Zen C puede compilarse a Objective-C (`.m`) usando la flag `--objc`, permitiéndote usar frameworks de Objective-C (como Cocoa/Foundation) y su sintaxis. diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 6fce2d2..217e9ec 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -197,6 +197,8 @@ let y: const int = 10; // 只读 (类型修饰) // y = 20; // 错误:无法赋值给 const 变量 ``` +> **类型推导**:Zen C 自动推导初始化变量的类型。在支持的编译器上编译为 C23 的 `auto`,否则使用 GCC 的 `__auto_type` 扩展。 + ### 2. 原始类型 | 类型 | C 等效类型 | 描述 | @@ -211,6 +213,8 @@ let y: const int = 10; // 只读 (类型修饰) | `char` | `char` | 单个字符 | | `string` | `char*` | C-string (以 null 结尾) | | `U0`, `u0`, `void` | `void` | 空类型 | +| `iN` (例 `i256`) | `_BitInt(N)` | 任意位宽有符号整数 (C23) | +| `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位宽无符号整数 (C23) | ### 3. 复合类型 @@ -1337,6 +1341,13 @@ let tid = local_id(); > **注意:** `--cuda` 标志设置 `nvcc` 为编译器并隐含 `--cpp` 模式。需要安装 NVIDIA CUDA Toolkit。 +### C23 支持 + +当使用兼容的后端编译器(GCC 14+, Clang 14+)时,Zen C 支持现代 C23特性。 + +- **`auto`**: 如果 `__STDC_VERSION__ >= 202300L`,Zen C 会自动将类型推导映射到标准 C23 `auto`。 +- **`_BitInt(N)`**: 使用 `iN` 和 `uN` 类型(例如 `i256`, `u12`, `i24`)访问 C23 任意位宽整数。 + ### Objective-C 互操作 Zen C 可以通过 `--objc` 标志编译为 Objective-C (`.m`),允许你使用 Objective-C 框架(如 Cocoa/Foundation)和语法。 diff --git a/README_ZH_TW.md b/README_ZH_TW.md index fc9cef5..8618540 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -197,6 +197,8 @@ let y: const int = 10; // 只讀 (類型修飾) // y = 20; // 錯誤:無法賦值給 const 變量 ``` +> **型別推導**:Zen C 自動推導初始化變數的型別。在支援的編譯器上編譯為 C23 的 `auto`,否則使用 GCC 的 `__auto_type` 擴充功能。 + ### 2. 原始類型 | 類型 | C 等效類型 | 描述 | @@ -211,6 +213,8 @@ let y: const int = 10; // 只讀 (類型修飾) | `char` | `char` | 單個字符 | | `string` | `char*` | C-string (以 null 結尾) | | `U0`, `u0`, `void` | `void` | 空類型 | +| `iN` (例 `i256`) | `_BitInt(N)` | 任意位元寬度有號整數 (C23) | +| `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位元寬度無號整數 (C23) | ### 3. 複合類型 @@ -1337,6 +1341,13 @@ let tid = local_id(); > **注意:** `--cuda` 標誌設置 `nvcc` 為編譯器並隱含 `--cpp` 模式。需要安裝 NVIDIA CUDA Toolkit。 +### C23 支援 + +當使用相容的後端編譯器(GCC 14+, Clang 14+)時,Zen C 支援現代 C23 特性。 + +- **`auto`**: 如果 `__STDC_VERSION__ >= 202300L`,Zen C 會自動將型別推導映射到標準 C23 `auto`。 +- **`_BitInt(N)`**: 使用 `iN` 和 `uN` 型別(例如 `i256`, `u12`, `i24`)存取 C23 任意位元寬度整數。 + ### Objective-C 互操作 Zen C 可以通過 `--objc` 標誌編譯為 Objective-C (`.m`),允許你使用 Objective-C 框架(如 Cocoa/Foundation)和語法。 diff --git a/src/ast/ast.c b/src/ast/ast.c index f4922a6..439a9f5 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -100,6 +100,7 @@ int is_integer_type(Type *t) t->kind == TYPE_I64 || t->kind == TYPE_U64 || t->kind == TYPE_USIZE || t->kind == TYPE_ISIZE || t->kind == TYPE_BYTE || t->kind == TYPE_RUNE || t->kind == TYPE_UINT || t->kind == TYPE_I128 || t->kind == TYPE_U128 || + t->kind == TYPE_BITINT || t->kind == TYPE_UBITINT || (t->kind == TYPE_STRUCT && t->name && (0 == strcmp(t->name, "int8_t") || 0 == strcmp(t->name, "uint8_t") || 0 == strcmp(t->name, "int16_t") || 0 == strcmp(t->name, "uint16_t") || @@ -262,6 +263,18 @@ static char *type_to_string_impl(Type *t) return xstrdup("int"); case TYPE_FLOAT: return xstrdup("float"); + case TYPE_BITINT: + { + char *res = xmalloc(32); + sprintf(res, "i%d", t->array_size); + return res; + } + case TYPE_UBITINT: + { + char *res = xmalloc(32); + sprintf(res, "u%d", t->array_size); + return res; + } case TYPE_POINTER: { @@ -452,6 +465,18 @@ static char *type_to_c_string_impl(Type *t) return xstrdup("int"); case TYPE_FLOAT: return xstrdup("float"); + case TYPE_BITINT: + { + char *res = xmalloc(32); + sprintf(res, "_BitInt(%d)", t->array_size); + return res; + } + case TYPE_UBITINT: + { + char *res = xmalloc(40); + sprintf(res, "unsigned _BitInt(%d)", t->array_size); + return res; + } case TYPE_POINTER: { diff --git a/src/ast/ast.h b/src/ast/ast.h index a868bf0..71d9943 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -59,6 +59,8 @@ typedef enum TYPE_FUNCTION, ///< Function pointer or reference. TYPE_GENERIC, ///< Generic type parameter (T). TYPE_ALIAS, ///< Opaque type alias. + TYPE_BITINT, ///< C23 _BitInt(N). + TYPE_UBITINT, ///< C23 unsigned _BitInt(N). TYPE_UNKNOWN ///< Unknown/unresolved type. } TypeKind; @@ -75,7 +77,7 @@ typedef struct Type int is_const; ///< 1 if const-qualified. int is_explicit_struct; ///< 1 if defined with "struct" keyword explicitly. int is_raw; // Raw function pointer (fn*) - int array_size; ///< Size for fixed-size arrays. + int array_size; ///< Size for fixed-size arrays. For TYPE_BITINT, this is the bit width. union { int is_varargs; ///< 1 if function type is variadic. diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 31513ef..0b78676 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -85,7 +85,11 @@ void emit_preamble(ParserContext *ctx, FILE *out) else { // C mode + fputs("#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202300L\n", out); + fputs("#define ZC_AUTO auto\n", out); + fputs("#else\n", out); fputs("#define ZC_AUTO __auto_type\n", out); + fputs("#endif\n", out); fputs("#define ZC_CAST(T, x) ((T)(x))\n", out); fputs(ZC_TCC_COMPAT_STR, out); fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : " diff --git a/src/codegen/compat.h b/src/codegen/compat.h index 63a5af5..f8d9a4e 100644 --- a/src/codegen/compat.h +++ b/src/codegen/compat.h @@ -14,7 +14,11 @@ #define ZC_EXTERN_C_END } #else /* C mode */ -#define ZC_AUTO __auto_type ///< Auto type inference. +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202300L +#define ZC_AUTO auto ///< C23 standard auto. +#else +#define ZC_AUTO __auto_type ///< GCC/Clang extension. +#endif #define ZC_CAST(T, x) ((T)(x)) ///< Explicit cast. #define ZC_REINTERPRET(T, x) ((T)(x)) ///< Reinterpret cast. #define ZC_EXTERN_C ///< Extern "C" (no-op in C). diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 51d2baa..6156cc0 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -148,6 +148,8 @@ int is_type_copy(ParserContext *ctx, Type *t) case TYPE_POINTER: // Pointers are Copy case TYPE_FUNCTION: case TYPE_ENUM: // Enums are integers + case TYPE_BITINT: + case TYPE_UBITINT: return 1; case TYPE_STRUCT: diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c index 65f2848..49e961c 100644 --- a/src/parser/parser_type.c +++ b/src/parser/parser_type.c @@ -300,6 +300,90 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) free(name); return type_new(TYPE_I16); } + + // C23 BitInt Support (i42, u256, etc.) + if ((name[0] == 'i' || name[0] == 'u') && isdigit(name[1])) + { + // Verify it is a purely numeric suffix + int valid = 1; + for (size_t k = 1; k < strlen(name); k++) + { + if (!isdigit(name[k])) + { + valid = 0; + break; + } + } + if (valid) + { + int width = atoi(name + 1); + if (width > 0) + { + // Map standard widths to standard types for standard ABI/C compabitility + if (name[0] == 'i') + { + if (width == 8) + { + free(name); + return type_new(TYPE_I8); + } + if (width == 16) + { + free(name); + return type_new(TYPE_I16); + } + if (width == 32) + { + free(name); + return type_new(TYPE_I32); + } + if (width == 64) + { + free(name); + return type_new(TYPE_I64); + } + if (width == 128) + { + free(name); + return type_new(TYPE_I128); + } + } + else + { + if (width == 8) + { + free(name); + return type_new(TYPE_U8); + } + if (width == 16) + { + free(name); + return type_new(TYPE_U16); + } + if (width == 32) + { + free(name); + return type_new(TYPE_U32); + } + if (width == 64) + { + free(name); + return type_new(TYPE_U64); + } + if (width == 128) + { + free(name); + return type_new(TYPE_U128); + } + } + + Type *t = type_new(name[0] == 'u' ? TYPE_UBITINT : TYPE_BITINT); + t->array_size = width; + free(name); + return t; + } + } + } if (strcmp(name, "u16") == 0) { free(name); -- cgit v1.2.3 From 472434301940015365f7ed303f52d71c505ac487 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 19:44:32 +0000 Subject: Improvements for the standard library + '@ctype'. --- Makefile | 2 +- README.md | 1 + README_ES.md | 1 + README_ZH_CN.md | 1 + README_ZH_TW.md | 1 + src/ast/ast.h | 2 ++ src/codegen/codegen_utils.c | 8 +++++- src/parser/parser.h | 3 ++- src/parser/parser_decl.c | 6 +++-- src/parser/parser_struct.c | 2 +- src/parser/parser_utils.c | 53 ++++++++++++++++++++++++++++++++++++-- std/core.zc | 6 ++--- std/cuda.zc | 2 ++ std/env.zc | 29 +++++++-------------- std/fs.zc | 62 ++++++++++++++++++--------------------------- std/io.zc | 43 ++++++++++++++++--------------- std/map.zc | 22 +++++++++------- std/net.zc | 25 ++++++++++-------- std/process.zc | 21 ++++++++------- std/set.zc | 20 +++++++-------- std/thread.zc | 5 ++++ std/time.zc | 3 +++ 22 files changed, 192 insertions(+), 126 deletions(-) (limited to 'src') diff --git a/Makefile b/Makefile index d15f556..b2d8e29 100644 --- a/Makefile +++ b/Makefile @@ -188,7 +188,7 @@ clean: @echo "=> Clean complete!" # Test -test: $(TARGET) +test: $(TARGET) $(PLUGINS) ./tests/run_tests.sh ./tests/run_codegen_tests.sh ./tests/run_example_transpile.sh diff --git a/README.md b/README.md index f03f605..001e590 100644 --- a/README.md +++ b/README.md @@ -939,6 +939,7 @@ Decorate functions and structs to modify compiler behavior. | `@host` | Fn | CUDA: Host function (`__host__`). | | `@comptime` | Fn | Helper function available for compile-time execution. | | `@derive(...)` | Struct | Auto-implement traits. Supports `Debug`, `Eq` (Smart Derive), `Copy`, `Clone`. | +| `@ctype("type")` | Fn Param | Overrides generated C type for a parameter. | | `@` | Any | Passes generic attributes to C (e.g. `@flatten`, `@alias("name")`). | #### Custom Attributes diff --git a/README_ES.md b/README_ES.md index 9040ea8..999a7b4 100644 --- a/README_ES.md +++ b/README_ES.md @@ -938,6 +938,7 @@ Decora funciones y structs para modificar el comportamiento del compilador. | `@host` | Fn | CUDA: Función de host (`__host__`). | | `@comptime` | Fn | Función auxiliar disponible para ejecución en tiempo de compilación. | | `@derive(...)` | Struct | Implementa traits automáticamente. Soporta `Debug`, `Eq` (Derivación Inteligente), `Copy`, `Clone`. | +| `@ctype("tipo")` | Parámetro Fn | Sobrescribe el tipo C generado para un parámetro. | | `@` | Cualquier | Pasa atributos genéricos a C (ej. `@flatten`, `@alias("nombre")`). | #### Atributos Personalizados diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 217e9ec..daa8a3d 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -938,6 +938,7 @@ let re = regex! { ^[a-z]+$ }; | `@host` | 函数 | CUDA: 主机函数 (`__host__`)。 | | `@comptime` | 函数 | 用于编译时执行的辅助函数。 | | `@derive(...)` | 结构体 | 自动实现 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@ctype("type")` | 函数参数 | 覆盖参数生成的 C 类型。 | | `@` | 任意 | 将泛型属性传递给 C (例如 `@flatten`, `@alias("name")`)。 | #### 自定义属性 diff --git a/README_ZH_TW.md b/README_ZH_TW.md index 8618540..2707caa 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -938,6 +938,7 @@ let re = regex! { ^[a-z]+$ }; | `@host` | 函數 | CUDA: 主機函數 (`__host__`)。 | | `@comptime` | 函數 | 用於編譯時執行的輔助函數。 | | `@derive(...)` | 結構體 | 自動實現 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@ctype("type")` | 函數參數 | 覆蓋參數生成的 C 類型。 | | `@` | 任意 | 將泛型屬性傳遞給 C (例如 `@flatten`, `@alias("name")`)。 | #### 自定義屬性 diff --git a/src/ast/ast.h b/src/ast/ast.h index 71d9943..4498d7c 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -229,6 +229,8 @@ struct ASTNode int cuda_device; // @device -> __device__ int cuda_host; // @host -> __host__ + char **c_type_overrides; // @ctype("...") per parameter + Attribute *attributes; // Custom attributes } func; diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 391ebd3..0d03661 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -714,7 +714,12 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } char *type_str = NULL; - if (func->func.arg_types && func->func.arg_types[i]) + // Check for @ctype override first + if (func->func.c_type_overrides && func->func.c_type_overrides[i]) + { + type_str = xstrdup(func->func.c_type_overrides[i]); + } + else if (func->func.arg_types && func->func.arg_types[i]) { type_str = codegen_type_to_string(func->func.arg_types[i]); } @@ -724,6 +729,7 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } const char *name = ""; + if (func->func.param_names && func->func.param_names[i]) { name = func->func.param_names[i]; diff --git a/src/parser/parser.h b/src/parser/parser.h index 262c359..23c2920 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -561,7 +561,8 @@ ASTNode *parse_arrow_lambda_multi(ParserContext *ctx, Lexer *l, char **param_nam * @brief Parses and converts arguments. */ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out, - Type ***types_out, char ***names_out, int *is_varargs_out); + Type ***types_out, char ***names_out, int *is_varargs_out, + char ***ctype_overrides_out); /** * @brief Checks if a file has been imported. diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c index c96ca36..93a124d 100644 --- a/src/parser/parser_decl.c +++ b/src/parser/parser_decl.c @@ -99,10 +99,11 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) int count; Type **arg_types; char **param_names; + char **ctype_overrides; int is_varargs = 0; - char *args = - parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, ¶m_names, &is_varargs); + char *args = parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, ¶m_names, + &is_varargs, &ctype_overrides); char *ret = "void"; Type *ret_type_obj = type_new(TYPE_VOID); @@ -191,6 +192,7 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) node->func.defaults = defaults; node->func.ret_type_info = ret_type_obj; node->func.is_varargs = is_varargs; + node->func.c_type_overrides = ctype_overrides; if (gen_param) { diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index 82dd346..109eeee 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -100,7 +100,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l) char **param_names = NULL; int is_varargs = 0; char *args = parse_and_convert_args(ctx, l, &defaults, &arg_count, &arg_types, ¶m_names, - &is_varargs); + &is_varargs, NULL); char *ret = xstrdup("void"); if (lexer_peek(l).type == TOK_ARROW) diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 48418b6..28d2c11 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -3392,7 +3392,8 @@ char *consume_and_rewrite(ParserContext *ctx, Lexer *l) } char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out, - Type ***types_out, char ***names_out, int *is_varargs_out) + Type ***types_out, char ***names_out, int *is_varargs_out, + char ***ctype_overrides_out) { Token t = lexer_next(l); if (t.type != TOK_LPAREN) @@ -3406,18 +3407,52 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, char **defaults = xmalloc(sizeof(char *) * 16); Type **types = xmalloc(sizeof(Type *) * 16); char **names = xmalloc(sizeof(char *) * 16); + char **ctype_overrides = xmalloc(sizeof(char *) * 16); for (int i = 0; i < 16; i++) { defaults[i] = NULL; types[i] = NULL; names[i] = NULL; + ctype_overrides[i] = NULL; } if (lexer_peek(l).type != TOK_RPAREN) { while (1) { + // Check for @ctype("...") before parameter + char *ctype_override = NULL; + if (lexer_peek(l).type == TOK_AT) + { + lexer_next(l); // eat @ + Token attr = lexer_next(l); + if (attr.type == TOK_IDENT && attr.len == 5 && strncmp(attr.start, "ctype", 5) == 0) + { + if (lexer_next(l).type != TOK_LPAREN) + { + zpanic_at(lexer_peek(l), "Expected ( after @ctype"); + } + Token ctype_tok = lexer_next(l); + if (ctype_tok.type != TOK_STRING) + { + zpanic_at(ctype_tok, "@ctype requires a string argument"); + } + // Extract string content (strip quotes) + ctype_override = xmalloc(ctype_tok.len - 1); + strncpy(ctype_override, ctype_tok.start + 1, ctype_tok.len - 2); + ctype_override[ctype_tok.len - 2] = 0; + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(lexer_peek(l), "Expected ) after @ctype string"); + } + } + else + { + zpanic_at(attr, "Unknown parameter attribute @%.*s", attr.len, attr.start); + } + } + Token t = lexer_next(l); // Handle 'self' if (t.type == TOK_IDENT && strncmp(t.start, "self", 4) == 0 && t.len == 4) @@ -3470,6 +3505,7 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, types[count] = type_new_ptr(type_new(TYPE_VOID)); add_symbol(ctx, "self", "void*", types[count]); } + ctype_overrides[count] = ctype_override; count++; } else @@ -3514,11 +3550,20 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, } else { - strcat(buf, type_str); + // Use @ctype override if present + if (ctype_override) + { + strcat(buf, ctype_override); + } + else + { + strcat(buf, type_str); + } strcat(buf, " "); strcat(buf, name); } + ctype_overrides[count] = ctype_override; count++; if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "=")) @@ -3593,6 +3638,10 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, *count_out = count; *types_out = types; *names_out = names; + if (ctype_overrides_out) + { + *ctype_overrides_out = ctype_overrides; + } return buf; } diff --git a/std/core.zc b/std/core.zc index f450517..7379db4 100644 --- a/std/core.zc +++ b/std/core.zc @@ -7,11 +7,11 @@ include let __zen_hash_seed: usize = 14695981039346656037; -raw { -void _zen_panic(const char* file, int line, const char* func, const char* msg) { +extern fn exit(code: int); + +fn _zen_panic(file: const char*, line: int, func: const char*, msg: const char*) { fprintf(stderr, "%s:%d (%s): Panic: %s\n", file, line, func, msg); exit(1); } -} #define panic(msg) _zen_panic(__FILE__, __LINE__, __func__, msg) \ No newline at end of file diff --git a/std/cuda.zc b/std/cuda.zc index c6a9403..8fc6545 100644 --- a/std/cuda.zc +++ b/std/cuda.zc @@ -101,6 +101,8 @@ fn cuda_ok() -> bool { } +// Minimal raw block: required for cudaDeviceProp struct field access +// The cudaDeviceProp struct cannot be declared in Zen-C without type conflicts raw { void _z_cuda_get_props(int dev, char* name, size_t* total_mem, int* sm_count, int* major, int* minor, int* max_threads, int* warp_size) { struct cudaDeviceProp prop; diff --git a/std/env.zc b/std/env.zc index 959784f..c63fd3d 100644 --- a/std/env.zc +++ b/std/env.zc @@ -2,23 +2,12 @@ import "./core.zc" import "./option.zc" import "./string.zc" -raw { - char *_z_env_get(char *name) { - return getenv(name); - } - - int _z_env_set(char *name, char *value, int overwrite) { - return setenv(name, value, overwrite); - } - - int _z_env_unset(char *name) { - return unsetenv(name); - } -} +include -extern fn _z_env_get(name: char*) -> char*; -extern fn _z_env_set(name: char*, value: char*, overwrite: int) -> int; -extern fn _z_env_unset(name: char*) -> int; +// Direct externs with const char* to match C stdlib declarations +extern fn getenv(name: const char*) -> char*; +extern fn setenv(name: const char*, value: const char*, overwrite: int) -> int; +extern fn unsetenv(name: const char*) -> int; @derive(Eq) enum EnvRes { @@ -30,7 +19,7 @@ struct Env {} impl Env { fn get(name: string) -> Option { - let value: string = _z_env_get(name); + let value: string = getenv(name); if (value == NULL) { return Option::None(); } @@ -39,7 +28,7 @@ impl Env { } fn get_dup(name: string) -> Option { - let value: string = _z_env_get(name); + let value: string = getenv(name); if (value == NULL) { return Option::None(); } @@ -52,13 +41,13 @@ impl Env { } fn set(name: string, value: string) -> EnvRes { - let ret: int = _z_env_set(name, value, 1); + let ret: int = setenv(name, value, 1); return (ret == 0) ? EnvRes::OK() : EnvRes::ERR(); } fn unset(name: string) -> EnvRes { - let ret: int = _z_env_unset(name); + let ret: int = unsetenv(name); return (ret == 0) ? EnvRes::OK() : EnvRes::ERR(); } diff --git a/std/fs.zc b/std/fs.zc index 4547b30..a00993b 100644 --- a/std/fs.zc +++ b/std/fs.zc @@ -14,12 +14,20 @@ include include include -// TODO: restructure this tomorrow (lol). +// Direct externs for simple functions with const char* parameters +extern fn access(pathname: const char*, mode: int) -> int; +extern fn unlink(pathname: const char*) -> int; +extern fn rmdir(pathname: const char*) -> int; +extern fn malloc(size: usize) -> void*; +extern fn free(ptr: void*); + +// Minimal raw block: required for opaque FILE*/DIR* types and C struct access +// These cannot be expressed in Zen-C extern declarations without type conflicts raw { typedef struct DirEntry* DirEntryPtr; - // Wrappers for FILE* handling due to opaque pointer casting - void* _z_fs_fopen(char* path, char* mode) { + // FILE* wrappers - fopen/fclose/etc use FILE* which conflicts with void* + void* _z_fs_fopen(const char* path, const char* mode) { return fopen(path, mode); } @@ -43,22 +51,8 @@ raw { 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); - } - - int _z_fs_unlink(char* pathname) { - return unlink(pathname); - } - - int _z_fs_rmdir(char* pathname) { - return rmdir(pathname); - } - - // Wrappers for DIR* handling - void* _z_fs_opendir(char* name) { + // DIR* wrappers - opendir/closedir/readdir use DIR* which conflicts with void* + void* _z_fs_opendir(const char* name) { return opendir(name); } @@ -66,8 +60,8 @@ raw { return closedir((DIR*)dir); } - // struct stat / struct dirent helpers - int _z_fs_get_metadata(char* path, uint64_t* size, int* is_dir, int* is_file) { + // struct stat access - cannot define matching Zen-C struct for stat + int _z_fs_get_metadata(const char* path, uint64_t* size, int* is_dir, int* is_file) { struct stat st; if (stat(path, &st) != 0) return -1; *size = st.st_size; @@ -76,6 +70,7 @@ raw { return 0; } + // struct dirent access - readdir returns struct dirent* int _z_fs_read_entry(void* dir, char* out_name, int buf_size, int* is_dir) { struct dirent* ent = readdir((DIR*)dir); if (!ent) return 0; @@ -85,7 +80,8 @@ raw { return 1; } - int _z_fs_mkdir(char* path) { + // mkdir has different signatures on Windows vs POSIX + int _z_fs_mkdir(const char* path) { #ifdef _WIN32 return mkdir(path); #else @@ -94,26 +90,18 @@ 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_mkdir(path: const char*) -> int; +extern fn _z_fs_get_metadata(path: const 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; -extern fn _z_fs_fopen(path: char*, mode: char*) -> void*; +extern fn _z_fs_fopen(path: const char*, mode: const char*) -> void*; extern fn _z_fs_fclose(stream: void*) -> int; extern fn _z_fs_fread(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; extern fn _z_fs_fwrite(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; extern fn _z_fs_fseek(stream: void*, offset: I64, whence: int) -> int; extern fn _z_fs_ftell(stream: void*) -> I64; -extern fn _z_fs_opendir(name: char*) -> void*; +extern fn _z_fs_opendir(name: const 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; - struct File { handle: void*; @@ -203,7 +191,7 @@ impl File { } fn exists(path: char*) -> bool { - return _z_fs_access(path, Z_F_OK) == 0; + return access(path, Z_F_OK) == 0; } fn metadata(path: char*) -> Result { @@ -230,14 +218,14 @@ impl File { } fn remove_file(path: char*) -> Result { - if (_z_fs_unlink(path) != 0) { + if (unlink(path) != 0) { return Result::Err("Failed to remove file"); } return Result::Ok(true); } fn remove_dir(path: char*) -> Result { - if (_z_fs_rmdir(path) != 0) { + if (rmdir(path) != 0) { return Result::Err("Failed to remove directory"); } return Result::Ok(true); diff --git a/std/io.zc b/std/io.zc index 2793ecf..cfb9179 100644 --- a/std/io.zc +++ b/std/io.zc @@ -2,28 +2,32 @@ import "./core.zc" import "./string.zc" -raw { - int _z_vprintf(char* fmt, va_list ap) { - return vprintf((const char*)fmt, ap); - } - - int _z_vsnprintf(char* str, size_t size, char* fmt, va_list ap) { - return vsnprintf(str, size, (const char*)fmt, ap); - } +include +include - void* _z_get_stdin() { return stdin; } - int _z_get_eof() { return EOF; } +// These work directly with const char* in extern declarations +extern fn vprintf(fmt: const char*, ap: va_list) -> int; +extern fn vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> int; + +// EOF is typically -1, but we define it for portability +def Z_EOF = -1; + +// Minimal raw block: only for truly opaque FILE* types that can't be +// represented in Zen-C extern declarations without type conflicts. +// These wrappers use void* to avoid FILE* declaration conflicts. +raw { + void* _z_get_stdin(void) { return stdin; } int _z_fgetc(void* stream) { return fgetc((FILE*)stream); } } -extern fn _z_vprintf(fmt: char*, ap: va_list) -> int; -extern fn _z_vsnprintf(str: char*, size: usize, fmt: char*, ap: va_list) -> int; +extern fn _z_get_stdin() -> void*; +extern fn _z_fgetc(stream: void*) -> int; fn format(fmt: char*, ...) -> char* { static let buffer: char[1024]; let ap: va_list; va_start(ap, fmt); - _z_vsnprintf(buffer, 1024, fmt, ap); + vsnprintf(buffer, 1024, fmt, ap); va_end(ap); return (char*)buffer; } @@ -31,7 +35,7 @@ fn format(fmt: char*, ...) -> char* { fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vsnprintf(buffer, size, fmt, ap); + let ret = vsnprintf(buffer, size, fmt, ap); va_end(ap); return ret; } @@ -42,7 +46,7 @@ fn format_new(fmt: char*, ...) -> char* { let ap: va_list; va_start(ap, fmt); - _z_vsnprintf(buffer, 1024, fmt, ap); + vsnprintf(buffer, 1024, fmt, ap); va_end(ap); return buffer; } @@ -50,7 +54,7 @@ fn format_new(fmt: char*, ...) -> char* { fn print(fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vprintf(fmt, ap); + let ret = vprintf(fmt, ap); va_end(ap); return ret; } @@ -58,7 +62,7 @@ fn print(fmt: char*, ...) -> int { fn println(fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vprintf(fmt, ap); + let ret = vprintf(fmt, ap); va_end(ap); puts(""); return ret + 1; @@ -72,11 +76,10 @@ fn readln() -> char* { let c: int; let std_in = _z_get_stdin(); - let eof_val = _z_get_eof(); while (true) { c = _z_fgetc(std_in); - if (c == eof_val) break; + if (c == Z_EOF) break; if (c == 10) break; // '\n' if (len + 1 >= cap) { @@ -93,7 +96,7 @@ fn readln() -> char* { len = len + 1; } - if (len == 0 && c == eof_val) { + if (len == 0 && c == Z_EOF) { free(line); return NULL; } diff --git a/std/map.zc b/std/map.zc index 70d6ad2..8376da2 100644 --- a/std/map.zc +++ b/std/map.zc @@ -3,16 +3,20 @@ import "./core.zc" import "./option.zc" import "./mem.zc" -raw { - extern size_t __zen_hash_seed; - size_t _map_hash_str(const char* str) { - size_t hash = __zen_hash_seed; - while (*str) { - hash ^= (unsigned char)*str++; - hash *= 1099511628211UL; - } - return hash; +// Pure Zen-C string hash using FNV-1a algorithm +fn _map_hash_str(str: const char*) -> usize { + let hash = __zen_hash_seed; + let i: usize = 0; + + while (str[i] != 0) { + // Cast char to U8 for unsigned byte value + let b: U8 = (U8)str[i]; + hash = hash ^ (usize)b; + hash = hash * (usize)1099511628211; + i = i + 1; } + + return hash; } struct Map { diff --git a/std/net.zc b/std/net.zc index dd10642..d410a4d 100644 --- a/std/net.zc +++ b/std/net.zc @@ -13,8 +13,17 @@ import "./mem.zc" def Z_AF_INET = 2; def Z_SOCK_STREAM = 1; +// Direct externs for simple socket functions +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; + +// Minimal raw block: required for struct sockaddr_in usage +// These functions encapsulate sockaddr_in setup because the struct layout +// cannot be declared in Zen-C without type conflicts with C headers. +// Also includes inet_pton, htons, bind, connect, listen, accept wrappers. raw { - static int _z_net_bind(int fd, char *host, int port) { + static int _z_net_bind(int fd, const char *host, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -28,7 +37,7 @@ raw { return 0; } - static int _z_net_connect(int fd, char *host, int port) { + static int _z_net_connect(int fd, const char *host, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -42,19 +51,15 @@ raw { return accept(fd, NULL, NULL); } - static ssize_t _z_net_write(int fd, char* buf, size_t n) { + static ssize_t _z_net_write(int fd, const char* buf, size_t n) { return write(fd, (const void*)buf, n); } } -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_bind(fd: int, host: const char*, port: int) -> int; +extern fn _z_net_connect(fd: int, host: const char*, port: int) -> int; extern fn _z_net_accept(fd: int) -> int; -extern fn _z_net_write(fd: int, buf: char*, n: usize) -> isize; +extern fn _z_net_write(fd: int, buf: const char*, n: usize) -> isize; struct TcpStream { diff --git a/std/process.zc b/std/process.zc index d0b09a9..3ce43b6 100644 --- a/std/process.zc +++ b/std/process.zc @@ -5,8 +5,16 @@ import "./mem.zc"; import "./string.zc"; import "./option.zc"; -raw { - void *_z_popen(char *command, char *type) { +include +include + +// system() can be externed directly with const char* +extern fn system(command: const char*) -> int; + +// Minimal raw block: only for opaque FILE* types +// popen/pclose/fgets use FILE* which conflicts with void* +raw { + void *_z_popen(const char *command, const char *type) { return (void *)popen(command, type); } @@ -17,16 +25,11 @@ raw { 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_popen(command: const char*, type: const 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; @@ -105,7 +108,7 @@ impl Command { fn status(self) -> int { let cmd_str = self._build_cmd(); - let code = _z_system(cmd_str.c_str()); + let code = system(cmd_str.c_str()); cmd_str.free(); return code; } diff --git a/std/set.zc b/std/set.zc index ba6c93f..e1faab3 100644 --- a/std/set.zc +++ b/std/set.zc @@ -2,17 +2,17 @@ import "./core.zc" import "./option.zc" -raw { - extern size_t __zen_hash_seed; - size_t _set_hash(const void* data, size_t len) { - size_t hash = __zen_hash_seed; - const unsigned char* bytes = (const unsigned char*)data; - for (size_t i = 0; i < len; i++) { - hash ^= bytes[i]; - hash *= 1099511628211UL; - } - return hash; +// Pure Zen-C generic hash using FNV-1a algorithm +fn _set_hash(data: const void*, len: usize) -> usize { + let hash = __zen_hash_seed; + let bytes: U8* = (U8*)data; + + for (let i: usize = 0; i < len; i = i + 1) { + hash = hash ^ (usize)bytes[i]; + hash = hash * (usize)1099511628211; } + + return hash; } struct Set { diff --git a/std/thread.zc b/std/thread.zc index 98f080e..16f3ca1 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -7,6 +7,11 @@ import "./core.zc" import "./result.zc" import "./mem.zc" +// Essential raw block: required for pthread operations and closure trampolining +// This block cannot be eliminated because: +// 1. z_closure_T is an internal compiler type for Zen-C closures +// 2. pthread_t, pthread_mutex_t are opaque types that can't be extern'd with void* +// 3. The trampoline function needs to cast and execute Zen-C closures raw { typedef void (*ZenThreadFunc)(void*); diff --git a/std/time.zc b/std/time.zc index 865dd6d..fa764ed 100644 --- a/std/time.zc +++ b/std/time.zc @@ -5,6 +5,9 @@ include include include +// Minimal raw block: required because gettimeofday() uses struct timeval +// which can't be declared in Zen-C without type conflicts, and time() +// has conflicting type signature (time_t* vs void*) raw { static uint64_t _time_now_impl(void) { struct timeval tv; -- 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 'src') 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 b61350d63260e9b9cf77cf2a2ce7145a9913f3a7 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 01:18:45 +0000 Subject: Fix for concurrency test failure --- src/codegen/codegen_utils.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src') diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 8d9cb28..39d1861 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -64,6 +64,18 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) } else if (generic && (!bracket || generic < bracket)) { + // Special case: Async should NOT be mangled to Async_T + // because it compiles to the opaque struct 'Async' (defined in codegen_decl) + if (strncmp(type_str, "Async<", 6) == 0) + { + fprintf(out, "Async %s", name); + if (bracket) + { + fprintf(out, "%s", bracket); + } + return; + } + char *gt = strchr(generic, '>'); if (gt) { -- cgit v1.2.3 From 4acbcb77483ff8fc127dca261864877675fa3b5a Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 01:30:34 +0000 Subject: Update codegen for robustness --- src/codegen/codegen.h | 4 ++-- src/codegen/codegen_decl.c | 6 +++--- src/codegen/codegen_main.c | 2 +- src/codegen/codegen_stmt.c | 2 +- src/codegen/codegen_utils.c | 49 +++++++++++++++++++++------------------------ 5 files changed, 30 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/codegen/codegen.h b/src/codegen/codegen.h index b3e971d..89614f7 100644 --- a/src/codegen/codegen.h +++ b/src/codegen/codegen.h @@ -48,7 +48,7 @@ char *replace_string_type(const char *args); const char *parse_original_method_name(const char *mangled); void emit_auto_type(ParserContext *ctx, ASTNode *init_expr, Token t, FILE *out); char *codegen_type_to_string(Type *t); -void emit_func_signature(FILE *out, ASTNode *func, const char *name_override); +void emit_func_signature(ParserContext *ctx, FILE *out, ASTNode *func, const char *name_override); char *strip_template_suffix(const char *name); int emit_move_invalidation(ParserContext *ctx, ASTNode *node, FILE *out); void codegen_expression_with_move(ParserContext *ctx, ASTNode *node, FILE *out); @@ -66,7 +66,7 @@ void emit_trait_defs(ASTNode *node, FILE *out); void emit_enum_protos(ASTNode *node, FILE *out); void emit_globals(ParserContext *ctx, ASTNode *node, FILE *out); void emit_lambda_defs(ParserContext *ctx, FILE *out); -void emit_protos(ASTNode *node, FILE *out); +void emit_protos(ParserContext *ctx, ASTNode *node, FILE *out); void emit_impl_vtables(ParserContext *ctx, FILE *out); /** diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 0b78676..9d23617 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -698,7 +698,7 @@ void emit_globals(ParserContext *ctx, ASTNode *node, FILE *out) } // Emit function prototypes -void emit_protos(ASTNode *node, FILE *out) +void emit_protos(ParserContext *ctx, ASTNode *node, FILE *out) { ASTNode *f = node; while (f) @@ -721,7 +721,7 @@ void emit_protos(ASTNode *node, FILE *out) } else { - emit_func_signature(out, f, NULL); + emit_func_signature(ctx, out, f, NULL); fprintf(out, ";\n"); } } @@ -799,7 +799,7 @@ void emit_protos(ASTNode *node, FILE *out) } else { - emit_func_signature(out, m, proto); + emit_func_signature(ctx, out, m, proto); fprintf(out, ";\n"); } diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c index a140070..b298700 100644 --- a/src/codegen/codegen_main.c +++ b/src/codegen/codegen_main.c @@ -616,7 +616,7 @@ void codegen_node(ParserContext *ctx, ASTNode *node, FILE *out) } } - emit_protos(merged_funcs, out); + emit_protos(ctx, merged_funcs, out); emit_impl_vtables(ctx, out); diff --git a/src/codegen/codegen_stmt.c b/src/codegen/codegen_stmt.c index 2f9a2ba..7828ecf 100644 --- a/src/codegen/codegen_stmt.c +++ b/src/codegen/codegen_stmt.c @@ -750,7 +750,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) { fprintf(out, "inline "); } - emit_func_signature(out, node, NULL); + emit_func_signature(ctx, out, node, NULL); fprintf(out, "\n"); fprintf(out, "{\n"); char *prev_ret = g_current_func_ret_type; diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 39d1861..08707cc 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -41,7 +41,7 @@ char *strip_template_suffix(const char *name) } // Helper to emit C declaration (handle arrays, function pointers correctly) -void emit_c_decl(FILE *out, const char *type_str, const char *name) +void emit_c_decl(ParserContext *ctx, FILE *out, const char *type_str, const char *name) { char *bracket = strchr(type_str, '['); char *generic = strchr(type_str, '<'); @@ -64,40 +64,38 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) } else if (generic && (!bracket || generic < bracket)) { - // Special case: Async should NOT be mangled to Async_T - // because it compiles to the opaque struct 'Async' (defined in codegen_decl) - if (strncmp(type_str, "Async<", 6) == 0) - { - fprintf(out, "Async %s", name); - if (bracket) - { - fprintf(out, "%s", bracket); - } - return; - } - + char mangled_candidate[256]; char *gt = strchr(generic, '>'); + int success = 0; + 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) + // Limit check + if (base_len + arg_len + 2 < 256) { - fprintf(out, "%s", bracket); + snprintf(mangled_candidate, 256, "%.*s_%.*s", base_len, type_str, arg_len, + generic + 1); + + if (find_struct_def_codegen(ctx, mangled_candidate)) + { + fprintf(out, "%s %s", mangled_candidate, name); + success = 1; + } } } - else + + if (!success) { int base_len = generic - type_str; fprintf(out, "%.*s %s", base_len, type_str, name); + } - if (bracket) - { - fprintf(out, "%s", bracket); - } + if (bracket) + { + fprintf(out, "%s", bracket); } } else if (bracket) @@ -114,8 +112,7 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) // Helper to emit variable declarations with array types. void emit_var_decl_type(ParserContext *ctx, FILE *out, const char *type_str, const char *var_name) { - (void)ctx; - emit_c_decl(out, type_str, var_name); + emit_c_decl(ctx, out, type_str, var_name); } // Find struct definition @@ -671,7 +668,7 @@ char *codegen_type_to_string(Type *t) } // Emit function signature using Type info for correct C codegen -void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) +void emit_func_signature(ParserContext *ctx, FILE *out, ASTNode *func, const char *name_override) { if (!func || func->type != NODE_FUNCTION) { @@ -763,7 +760,7 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } // check if array type - emit_c_decl(out, type_str, name); + emit_c_decl(ctx, out, type_str, name); free(type_str); } if (func->func.is_varargs) -- cgit v1.2.3 From 71297486445992de9d97affabc77908da79e8c89 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 01:33:46 +0000 Subject: Fix compilation error --- src/parser/parser_stmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 4c24de3..7758ae3 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -3305,7 +3305,7 @@ char *run_comptime_block(ParserContext *ctx, Lexer *l) ASTNode *fn = ref->node; if (fn && fn->type == NODE_FUNCTION && fn->func.is_comptime) { - emit_func_signature(f, fn, NULL); + emit_func_signature(ctx, f, fn, NULL); fprintf(f, ";\n"); codegen_node_single(ctx, fn, f); } -- cgit v1.2.3 From 051400c70a4d5384923113cfbcbc69e8e58d27a0 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 11:58:40 +0000 Subject: Github copilot instructions --- .github/copilot-instructions.md | 87 +++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- src/parser/parser_expr.c | 1 + 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 .github/copilot-instructions.md (limited to 'src') diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..fac102a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,87 @@ +# Zen C Copilot Instructions + +These instructions are **MANDATORY** for all code generation and review tasks in the Zen C project. + +## 1. Memory Management (Critical) +* **Arena Allocation**: The compiler uses a bump-pointer arena allocator. + * **MUST USE**: `xmalloc`, `xcalloc`, `xrealloc`, `xstrdup` (defined in `src/utils/utils.c`). + * **NEVER USE**: Standard `malloc`, `calloc`, `free` (unless interfacing with an external library that strictly requires owned heap memory, like CJSON). +* **Destructors/Freeing**: + * `free(ptr)` is `#defined` to `((void)0)` in `src/zprep.h`. It is a no-op. + * Do **NOT** attempt to free AST nodes or types. `ast_free` is a no-op. + * Memory is reclaimed only when the process exits. Design your data structures accordingly (append-only is fine). + +## 2. AST and Type System +* **Creation**: Use `ast_create(NODE_TYPE)` to allocate new nodes. +* **Type Representation**: + * Use `Type` struct (defined in `src/ast/ast.h`). + * Use `type_new(TYPE_KIND)` helper. + * `type_to_string(t)` and `type_to_c_string(t)` return arena-allocated strings. Do not worry about freeing them. +* **Traversal**: + * The compiler uses **Recursive Descent** with **Switch Statements** on `node->type`. + * Do NOT introduce Visitor patterns or callback tables unless consistent with existing code. + +## 3. Parser Patterns +* **Context**: `ParserContext *ctx` is the god-object. It MUST be passed to almost every function in parsing, analysis, and codegen. + * **Signature Rule**: `ReturnType func_name(ParserContext *ctx, ...)` +* **Token Consumption**: + * Use `expect(lexer, TOKEN_TYPE, "error message")` for mandatory tokens. + * For optional tokens, check `l->token.type` and assume `lexer_next(l)` is used to advance (verify specific helper availability). +* **Error Handling**: + * **Fatal**: `zpanic("msg")` or `zpanic_at(token, "msg")`. Exits immediately (or delegates to LSP handler). + * **Warning**: `zwarn("msg")` or `zwarn_at(token, "msg")`. + * **Semantic Errors**: Prefer `zpanic_at` for type errors to give line/col info. + +## 4. Code Generation (C Backend) +* **Generic Mangling**: + * Generic structs (e.g., `Slice`) are mangled to `Slice_int` **IF AND ONLY IF** the instantiated struct exists (checked via `find_struct_def_codegen`). + * **Fallback**: If the mangled struct (e.g. `Async_int`) does not exist, use the base name (`Async`). This handles opaque types like `Async` correctly. + * See `emit_c_decl` in `src/codegen/codegen_utils.c` for the canonical implementation. +* **Function Signatures**: + * Use `emit_func_signature(ctx, out, node, override_name)` to handle modifiers, return types, and arguments correctly. +* **Output**: Use `fprintf(out, ...)` where `out` is the `FILE*`. + +## 5. Coding Style & Conventions +* **Formatting**: + * Indentation: 4 spaces. + * Braces: **ALWAYS** Use braces `{}` for control flow (`if`, `while`, `for`), even for single lines. + * Opening brace on the **NEXT** line (Allman style). +* **Naming**: + * Structs/Enums: `PascalCase`. + * Functions/Variables: `snake_case`. + * Macros/Constants: `SCREAMING_SNAKE_CASE`. + * Private/Static: No strict prefix, but `static` keyword is mandatory for internal file-scope functions. +* **Iterators**: + * When implementing iteration in compiler (C code): Use `while (node) { ... node = node->next; }` for linked lists (`ASTNode`, `StructRef`). + +## 6. Standard Library (Zen C Definitions) +* **Arrays**: Use `for val in arr` syntax (direct iteration). +* **Vectors**: `Vec` is a dynamic array. +* **Strings**: `string` in Zen C maps to `char*` in C. `kstring` or `zstr` are higher-level wrappers. + +## 7. Common Pitfalls +* **Unused Variables**: The compiler builds with `-Wall -Werror` (or similar strictness). Cast unused vars to void: `(void)var_name;`. +## 8. Zen C Language Rules (For writing .zc files) +* **Syntax**: + * Variables: `let x = 10;`, `let y: const int = 20;`. + * Constants: `def MAX = 100;` (compile-time). + * Functions: `fn name(arg: type) -> ret { ... }`. + * Structs: `struct Point { x: int; y: int; }`. + * Enums: `enum Shape { Circle(float), Rect(float, float) }`. +* **Memory & Resources**: + * **Move Semantics**: Structs/Enums are moved by default on assignment/pass-by-value. + * **Defer**: Use `defer stmt;` to run cleanup at scope exit. + * **Drop**: Implement `impl Drop for T` for RAII. +* **Arrays & Slices**: + * **Iteration**: Use `for val in arr` (direct iteration supported). + * **Slices**: `Slice` is a view. `int[N]` auto-converts to slice in loops. +* **Generics**: + * Syntax: `struct Box { val: T; }`. +* **Concurrency**: + * Use `async fn` and `await` keyword. + * `Async` is the opaque future type. +* **Standard Library**: + * Import with `import "std/io.zc"`, `import "std/vec.zc"`. + * Use `println "msg"` (shorthand) or `printf`. +* **Testing**: + * Use `test "name" { ... }` blocks for unit tests. diff --git a/README.md b/README.md index ecdf8fc..bf4962e 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ zc repl ### Environment Variables -You can set `ZC_ROOT` to specify the location of the Standard Library (standard imports like `import "std/vector.zc"`). This allows you to run `zc` from any directory. +You can set `ZC_ROOT` to specify the location of the Standard Library (standard imports like `import "std/vec.zc"`). This allows you to run `zc` from any directory. ```bash export ZC_ROOT=/path/to/Zen-C diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 6156cc0..28dc465 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -5421,6 +5421,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) // This gives a warning as "unused" but it's needed for the rewrite. char *r_name = resolve_struct_name_from_type(ctx, rhs->type_info, &is_rhs_ptr, &r_alloc); + (void)r_name; if (r_alloc) { free(r_alloc); -- 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 'src') 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 From aced94a89dd732d8ae8fddd27de9e1f1094c449a Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 15:48:07 +0000 Subject: Fix for #158 --- src/parser/parser_expr.c | 10 ++++++++++ std/slice.zc | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 28dc465..7c53d96 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -5644,7 +5644,17 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) char *t1 = type_to_string(lhs->type_info); char *t2 = type_to_string(rhs->type_info); // Skip type check if either operand is void* (escape hatch type) + // or if either operand is a generic type parameter (T, K, V, etc.) int skip_check = (strcmp(t1, "void*") == 0 || strcmp(t2, "void*") == 0); + if (lhs->type_info->kind == TYPE_GENERIC || rhs->type_info->kind == TYPE_GENERIC) + { + skip_check = 1; + } + // Also check if type name is a single uppercase letter (common generic param) + if ((strlen(t1) == 1 && isupper(t1[0])) || (strlen(t2) == 1 && isupper(t2[0]))) + { + skip_check = 1; + } // Allow comparing pointers/strings with integer literal 0 (NULL) if (!skip_check) diff --git a/std/slice.zc b/std/slice.zc index 3c317ca..c757fbd 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -28,8 +28,8 @@ impl SliceIter { } impl Slice { - fn from_array(arr: T*, len: usize) -> Slice { - return Slice { data: arr, len: len }; + fn from_array(ptr: T*, len: usize) -> Slice { + return Slice { data: ptr, len: len }; } // Alias for backwards compatibility with std/mem.zc -- cgit v1.2.3 From 64c2bf1abc85fd5f5cbcb2a8491849663b37f98d Mon Sep 17 00:00:00 2001 From: rwusmm Date: Thu, 29 Jan 2026 17:21:20 +0200 Subject: Improved codegen as much as i could Fixed buffer overflows by replacing sprintf with snprintf in error handling Added memory cleanup for dynamically allocated strings (free t1, type, inferred, etc.) Removed duplicate code in the comparison logic for string pointers Improved error messages with better formatting and safer string handling Consolidated conditions in the member access logic for better readability Fixed potential memory leaks by freeing allocated suffix strings Removed redundant comments and optimized loop structures Better type checking with proper null terminator handling (ptr = '\0' instead ofptr = 0) Safer string operations with proper bounds checking --- src/codegen/codegen.c | 198 ++++++++++++++++++-------------------------------- 1 file changed, 70 insertions(+), 128 deletions(-) (limited to 'src') diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 7a67428..53373e9 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -1,4 +1,3 @@ - #include "codegen.h" #include "zprep.h" #include "../constants.h" @@ -59,9 +58,9 @@ static void codegen_var_expr(ParserContext *ctx, ASTNode *node, FILE *out) if (node->var_ref.suggestion && !ctx->silent_warnings) { char msg[256]; - sprintf(msg, "Undefined variable '%s'", node->var_ref.name); char help[256]; - sprintf(help, "Did you mean '%s'?", node->var_ref.suggestion); + snprintf(msg, sizeof(msg), "Undefined variable '%s'", node->var_ref.name); + snprintf(help, sizeof(help), "Did you mean '%s'?", node->var_ref.suggestion); zwarn_at(node->token, "%s\n = help: %s", msg, help); } } @@ -192,7 +191,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else if ((strcmp(node->binary.op, "==") == 0 || strcmp(node->binary.op, "!=") == 0)) { char *t1 = infer_type(ctx, node->binary.left); - int is_ptr = 0; if (t1) { @@ -207,19 +205,16 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } int resolved = 0; ASTNode *alias = global_user_structs; - if (alias) + while (alias) { - while (alias) + if (alias->type == NODE_TYPE_ALIAS && + strcmp(check, alias->type_alias.alias) == 0) { - if (alias->type == NODE_TYPE_ALIAS && - strcmp(check, alias->type_alias.alias) == 0) - { - check = alias->type_alias.original_type; - resolved = 1; - break; - } - alias = alias->next; + check = alias->type_alias.original_type; + resolved = 1; + break; } + alias = alias->next; } if (!resolved) { @@ -229,10 +224,9 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } int is_basic = IS_BASIC_TYPE(t1); - ASTNode *def = t1 ? find_struct_def(ctx, t1) : NULL; - if (t1 && def && (def->type == NODE_STRUCT || def->type == NODE_ENUM) && !is_basic && - !is_ptr) + + if (t1 && def && (def->type == NODE_STRUCT || def->type == NODE_ENUM) && !is_basic && !is_ptr) { char *base = t1; if (strncmp(base, "struct ", 7) == 0) @@ -285,8 +279,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else if (t1 && (strcmp(t1, "string") == 0 || strcmp(t1, "char*") == 0 || strcmp(t1, "const char*") == 0)) { - // Check if comparing to NULL - don't use strcmp for NULL comparisons - char *t2 = infer_type(ctx, node->binary.right); int is_null_compare = 0; if (node->binary.right->type == NODE_EXPR_VAR && strcmp(node->binary.right->var_ref.name, "NULL") == 0) @@ -299,8 +291,15 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) is_null_compare = 1; } - if (!is_null_compare && strcmp(t1, "string") == 0 && t2 && - strcmp(t2, "string") == 0) + if (is_null_compare) + { + fprintf(out, "("); + codegen_expression(ctx, node->binary.left, out); + fprintf(out, " %s ", node->binary.op); + codegen_expression(ctx, node->binary.right, out); + fprintf(out, ")"); + } + else { fprintf(out, "(strcmp("); codegen_expression(ctx, node->binary.left, out); @@ -315,19 +314,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, ") != 0)"); } } - else - { - // Direct pointer comparison - fprintf(out, "("); - codegen_expression(ctx, node->binary.left, out); - fprintf(out, " %s ", node->binary.op); - codegen_expression(ctx, node->binary.right, out); - fprintf(out, ")"); - } - if (t2) - { - free(t2); - } } else { @@ -337,6 +323,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) codegen_expression(ctx, node->binary.right, out); fprintf(out, ")"); } + if (t1) free(t1); } else { @@ -394,14 +381,13 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } // Check for Static Enum Variant Call: Enum.Variant(...) - if (target->type == NODE_EXPR_VAR) { ASTNode *def = find_struct_def(ctx, target->var_ref.name); if (def && def->type == NODE_ENUM) { char mangled[256]; - sprintf(mangled, "%s_%s", target->var_ref.name, method); + snprintf(mangled, sizeof(mangled), "%s_%s", target->var_ref.name, method); FuncSig *sig = find_func(ctx, mangled); if (sig) { @@ -410,15 +396,13 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) int arg_idx = 0; while (arg) { - if (arg_idx > 0 && arg) + if (arg_idx > 0) { fprintf(out, ", "); } - Type *param_t = - (arg_idx < sig->total_args) ? sig->arg_types[arg_idx] : NULL; + Type *param_t = (arg_idx < sig->total_args) ? sig->arg_types[arg_idx] : NULL; - // Tuple Packing Logic if (param_t && param_t->kind == TYPE_STRUCT && strncmp(param_t->name, "Tuple_", 6) == 0 && sig->total_args == 1 && node->call.arg_count > 1) @@ -436,7 +420,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) arg = arg->next; } fprintf(out, "}"); - break; // All args consumed + break; } codegen_expression(ctx, arg, out); @@ -456,7 +440,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) char *ptr = strchr(clean, '*'); if (ptr) { - *ptr = 0; + *ptr = '\0'; } char *base = clean; @@ -518,33 +502,30 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) int need_cast = 0; char mixin_func_name[128]; - sprintf(mixin_func_name, "%s__%s", call_base, method); + snprintf(mixin_func_name, sizeof(mixin_func_name), "%s__%s", call_base, method); char *resolved_method_suffix = NULL; if (!find_func(ctx, mixin_func_name)) { - // Try resolving as a trait method: Struct__Trait_Method StructRef *ref = ctx->parsed_impls_list; while (ref) { - if (ref->node && ref->node->type == NODE_IMPL_TRAIT) + if (ref->node && ref->node->type == NODE_IMPL_TRAIT && + strcmp(ref->node->impl_trait.target_type, base) == 0) { - if (strcmp(ref->node->impl_trait.target_type, base) == 0) + char trait_mangled[256]; + snprintf(trait_mangled, sizeof(trait_mangled), "%s__%s_%s", base, + ref->node->impl_trait.trait_name, method); + if (find_func(ctx, trait_mangled)) { - char trait_mangled[256]; - sprintf(trait_mangled, "%s__%s_%s", base, - ref->node->impl_trait.trait_name, method); - if (find_func(ctx, trait_mangled)) - { - char *suffix = - xmalloc(strlen(ref->node->impl_trait.trait_name) + - strlen(method) + 2); - sprintf(suffix, "%s_%s", ref->node->impl_trait.trait_name, - method); - resolved_method_suffix = suffix; - break; - } + size_t suffix_len = strlen(ref->node->impl_trait.trait_name) + + strlen(method) + 2; + char *suffix = xmalloc(suffix_len); + snprintf(suffix, suffix_len, "%s_%s", + ref->node->impl_trait.trait_name, method); + resolved_method_suffix = suffix; + break; } } ref = ref->next; @@ -559,15 +540,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) if (it->impl_node && it->impl_node->type == NODE_IMPL_TRAIT) { tname = it->impl_node->impl_trait.trait_name; - } - if (tname) - { char trait_mangled[512]; - sprintf(trait_mangled, "%s__%s_%s", base, tname, method); + snprintf(trait_mangled, sizeof(trait_mangled), + "%s__%s_%s", base, tname, method); if (find_func(ctx, trait_mangled)) { - char *suffix = xmalloc(strlen(tname) + strlen(method) + 2); - sprintf(suffix, "%s_%s", tname, method); + size_t suffix_len = strlen(tname) + strlen(method) + 2; + char *suffix = xmalloc(suffix_len); + snprintf(suffix, suffix_len, "%s_%s", tname, method); resolved_method_suffix = suffix; break; } @@ -582,15 +562,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } else { - // Method not found on primary struct, check mixins ASTNode *def = find_struct_def(ctx, base); if (def && def->type == NODE_STRUCT && def->strct.used_structs) { for (int k = 0; k < def->strct.used_struct_count; k++) { char mixin_check[128]; - sprintf(mixin_check, "%s__%s", def->strct.used_structs[k], - method); + snprintf(mixin_check, sizeof(mixin_check), "%s__%s", + def->strct.used_structs[k], method); if (find_func(ctx, mixin_check)) { call_base = def->strct.used_structs[k]; @@ -620,11 +599,19 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) arg = arg->next; } fprintf(out, ")"); + + if (resolved_method_suffix) + { + free(resolved_method_suffix); + } } free(clean); + free(type); return; } + if (type) free(type); } + if (node->call.callee->type == NODE_EXPR_VAR) { ASTNode *def = find_struct_def(ctx, node->call.callee->var_ref.name); @@ -680,26 +667,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) if (node->call.arg_names && node->call.callee->type == NODE_EXPR_VAR) { - char *fn_name = node->call.callee->var_ref.name; - FuncSig *sig = find_func(ctx, fn_name); - - if (sig && sig->arg_types) - { - for (int p = 0; p < sig->total_args; p++) - { - ASTNode *arg = node->call.args; - - for (int i = 0; i < node->call.arg_count && arg; i++, arg = arg->next) - { - if (node->call.arg_names[i] && p < node->call.arg_count) - { - - // For now, emit in order provided... - } - } - } - } - ASTNode *arg = node->call.args; int first = 1; while (arg) @@ -746,11 +713,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) strncmp(param_t->name, "Tuple_", 6) == 0 && sig->total_args == 1 && node->call.arg_count > 1) { - // Implicit Tuple Packing: - // Function expects 1 Tuple argument, but call has multiple args -> Pack - // them fprintf(out, "(%s){", param_t->name); - ASTNode *curr = arg; int first_field = 1; while (curr) @@ -765,8 +728,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } fprintf(out, "}"); handled = 1; - - // Advance main loop iterator to end arg = NULL; } } @@ -775,7 +736,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { if (arg == NULL) { - break; // Tuple packed all args + break; } } else @@ -800,16 +761,12 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) case NODE_EXPR_MEMBER: if (strcmp(node->member.field, "len") == 0) { - if (node->member.target->type_info) + if (node->member.target->type_info && + node->member.target->type_info->kind == TYPE_ARRAY && + node->member.target->type_info->array_size > 0) { - if (node->member.target->type_info->kind == TYPE_ARRAY) - { - if (node->member.target->type_info->array_size > 0) - { - fprintf(out, "%d", node->member.target->type_info->array_size); - break; - } - } + fprintf(out, "%d", node->member.target->type_info->array_size); + break; } } @@ -831,26 +788,15 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } else { - if (node->member.target->type == NODE_EXPR_CAST) - { - fprintf(out, "("); - } codegen_expression(ctx, node->member.target, out); - if (node->member.target->type == NODE_EXPR_CAST) - { - fprintf(out, ")"); - } - // Verify actual type instead of trusting is_pointer_access flag char *lt = infer_type(ctx, node->member.target); int actually_ptr = 0; if (lt && (lt[strlen(lt) - 1] == '*' || strstr(lt, "*"))) { actually_ptr = 1; } - if (lt) - { - free(lt); - } + if (lt) free(lt); + char *field = node->member.field; if (field && field[0] >= '0' && field[0] <= '9') { @@ -873,7 +819,8 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) is_slice_struct = 1; } } - if (node->index.array->resolved_type) + + if (!is_slice_struct && node->index.array->resolved_type) { if (strncmp(node->index.array->resolved_type, "Slice_", 6) == 0) { @@ -888,10 +835,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { is_slice_struct = 1; } - if (inferred) - { - free(inferred); - } + if (inferred) free(inferred); } if (is_slice_struct) @@ -990,7 +934,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, "/* UNSAFE: Full Slice on unknown size */ 0; "); + fprintf(out, "0; "); } } @@ -1006,6 +950,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, "(Slice_%s){ .data = _arr + _start, .len = _len, .cap = _len }; })", tname); } + if (tname && strcmp(tname, "unknown") != 0) free(tname); break; } case NODE_BLOCK: @@ -1096,9 +1041,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) break; case NODE_PLUGIN: { - // Plugin registry - declare external plugins ZPlugin *found = zptr_find_plugin(node->plugin_stmt.plugin_name); - if (found) { ZApi api = {.filename = g_current_filename ? g_current_filename : "input.zc", @@ -1211,20 +1154,19 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { Type *t = node->reflection.target_type; if (node->reflection.kind == 0) - { // @type_name + { char *s = codegen_type_to_string(t); fprintf(out, "\"%s\"", s); free(s); } else - { // @fields + { if (t->kind != TYPE_STRUCT || !t->name) { fprintf(out, "((void*)0)"); break; } char *sname = t->name; - // Find definition ASTNode *def = find_struct_def(ctx, sname); if (!def) { -- cgit v1.2.3 From 91ed9fdd65e09bd6cd32e44dd07c390f2cf79c22 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Sat, 31 Jan 2026 17:06:50 +0000 Subject: Fix codegen regressions: casting precedence and process segfault --- src/codegen/codegen.c | 16 ++++++++++++++-- src/codegen/codegen_decl.c | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 53373e9..37415c2 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -290,6 +290,18 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { is_null_compare = 1; } + else if (node->binary.right->type == NODE_EXPR_LITERAL && + node->binary.right->literal.type_kind == LITERAL_INT && + node->binary.right->literal.int_val == 0) + { + is_null_compare = 1; + } + else if (node->binary.left->type == NODE_EXPR_LITERAL && + node->binary.left->literal.type_kind == LITERAL_INT && + node->binary.left->literal.int_val == 0) + { + is_null_compare = 1; + } if (is_null_compare) { @@ -1121,9 +1133,9 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) break; } case NODE_EXPR_CAST: - fprintf(out, "(%s)(", node->cast.target_type); + fprintf(out, "((%s)(", node->cast.target_type); codegen_expression(ctx, node->cast.expr, out); - fprintf(out, ")"); + fprintf(out, "))"); break; case NODE_EXPR_SIZEOF: if (node->size_of.target_type) diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 9d23617..31bd2ee 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -50,6 +50,7 @@ void emit_preamble(ParserContext *ctx, FILE *out) else { // Standard hosted preamble. + fputs("#define _GNU_SOURCE\n", out); fputs("#include \n#include \n#include " "\n#include \n", out); -- cgit v1.2.3