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 --- README.md | 4 +--- src/codegen/codegen_stmt.c | 8 ++++---- src/repl/repl.c | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0c5bd0a..d59c3ad 100644 --- a/README.md +++ b/README.md @@ -1267,10 +1267,8 @@ make test ## Attributions -This project uses the following third-party libraries: - +This project uses third-party libraries. Full license texts can be found in the `LICENSES/` directory. * **[cJSON](https://github.com/DaveGamble/cJSON)** (MIT License): Used for JSON parsing and generation in the Language Server. - * Copyright (c) 2009-2017 Dave Gamble and cJSON contributors * **[zc-ape](https://github.com/OEvgeny/zc-ape)** (MIT License): The original Actually Portable Executable port of Zen-C by [Eugene Olonov](https://github.com/OEvgeny). * **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (ISC License): The foundational library that makes APE possible. 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(-) 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 b8834bc878f47b2cee02588607beeac1d1ae1246 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Tue, 27 Jan 2026 23:25:25 +0000 Subject: Added script for checking examples with 'make test' too --- Makefile | 1 + tests/run_example_transpile.sh | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100755 tests/run_example_transpile.sh diff --git a/Makefile b/Makefile index 776581d..d15f556 100644 --- a/Makefile +++ b/Makefile @@ -191,6 +191,7 @@ clean: test: $(TARGET) ./tests/run_tests.sh ./tests/run_codegen_tests.sh + ./tests/run_example_transpile.sh # Build with alternative compilers zig: diff --git a/tests/run_example_transpile.sh b/tests/run_example_transpile.sh new file mode 100755 index 0000000..c08a3ea --- /dev/null +++ b/tests/run_example_transpile.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +ZC="./zc" +EXAMPLES_DIR="examples" +FAIL_COUNT=0 +PASS_COUNT=0 + +echo "Running Example Transpilation Tests..." + +while IFS= read -r file; do + echo -n "Transpiling $file... " + + OUTPUT=$($ZC transpile "$file" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -eq 0 ]; then + echo "PASS" + PASS_COUNT=$((PASS_COUNT + 1)) + [ -f "out.c" ] && rm "out.c" + [ -f "a.out" ] && rm "a.out" + else + echo "FAIL" + echo "$OUTPUT" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + +done < <(find "$EXAMPLES_DIR" -name "*.zc") + +echo "----------------------------------------" +echo "Summary:" +echo "-> Passed: $PASS_COUNT" +echo "-> Failed: $FAIL_COUNT" +echo "----------------------------------------" + +if [ $FAIL_COUNT -ne 0 ]; then + exit 1 +fi + +exit 0 -- cgit v1.2.3 From 6a28507b59c4a5b89562962817b45e517f442cd4 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Tue, 27 Jan 2026 23:46:47 +0000 Subject: Fix for #143 --- src/codegen/codegen.c | 8 ++++++++ tests/memory/test_unsafe.zc | 15 +++++++++++++++ 2 files changed, 23 insertions(+) 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 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(-) 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(-) 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(-) 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 7ebc9c94207ef3e17e86c629c333444869e2a112 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Wed, 28 Jan 2026 11:21:30 +0000 Subject: Related to #137 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d59c3ad..9550208 100644 --- a/README.md +++ b/README.md @@ -478,6 +478,11 @@ Zen C supports operator overloading for user-defined structs by implementing spe | **Index** | `a[i]` | `get(a, i)` | | | `a[i] = v` | `set(a, i, v)` | +> **Note on String Equality**: +> - `string == string` performs **value comparison** (equivalent to `strcmp`). +> - `char* == char*` performs **pointer comparison** (checks memory addresses). +> - Mixed comparisons (e.g. `string == char*`) default to **pointer comparison**. + **Example:** ```zc impl Point { -- 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(-) 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 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(-) 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(-) 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 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 b7debea921e0de41bd218c099ec8f6250b15f7f1 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Thu, 29 Jan 2026 13:26:12 +0000 Subject: Fix portability issue macOS --- std/fs.zc | 6 +++--- std/time.zc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/std/fs.zc b/std/fs.zc index 7a54005..4547b30 100644 --- a/std/fs.zc +++ b/std/fs.zc @@ -14,7 +14,7 @@ include include include -// TODO: restructure this tomorrow. +// TODO: restructure this tomorrow (lol). raw { typedef struct DirEntry* DirEntryPtr; @@ -105,8 +105,8 @@ extern fn _z_fs_fopen(path: char*, mode: 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: long, whence: int) -> int; -extern fn _z_fs_ftell(stream: void*) -> long; +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_closedir(dir: void*) -> int; diff --git a/std/time.zc b/std/time.zc index 8e7cc6c..865dd6d 100644 --- a/std/time.zc +++ b/std/time.zc @@ -12,8 +12,8 @@ raw { return (uint64_t)(tv.tv_sec) * 1000 + (uint64_t)(tv.tv_usec) / 1000; } - static long _z_time_time(void) { - return (long)time(NULL); + static int64_t _z_time_time(void) { + return (int64_t)time(NULL); } } @@ -21,7 +21,7 @@ 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; +extern fn _z_time_time() -> I64; struct Duration { millis: U64; -- cgit v1.2.3 From 12932210150c6cbf1125b0c834b1425327b24911 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Thu, 29 Jan 2026 14:03:52 +0000 Subject: More languages --- README.md | 6 + README_ES.md | 1415 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README_ZH_CN.md | 1415 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README_ZH_TW.md | 1415 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 4251 insertions(+) create mode 100644 README_ES.md create mode 100644 README_ZH_CN.md create mode 100644 README_ZH_TW.md diff --git a/README.md b/README.md index e295eb5..2745aba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@
+[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +
+ +
+ # Zen C **Modern Ergonomics. Zero Overhead. Pure C.** diff --git a/README_ES.md b/README_ES.md new file mode 100644 index 0000000..2735497 --- /dev/null +++ b/README_ES.md @@ -0,0 +1,1415 @@ + +
+ +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +
+ +
+ +# Zen C + +**Ergonomía Moderna. Cero Sobrecarga. C Puro.** + +[![Estado de la Construcción](https://img.shields.io/badge/build-passing-brightgreen)]() +[![Licencia](https://img.shields.io/badge/license-MIT-blue)]() +[![Versión](https://img.shields.io/badge/version-0.1.0-orange)]() +[![Plataforma](https://img.shields.io/badge/platform-linux-lightgrey)]() + +*Escribe como un lenguaje de alto nivel, ejecuta como C.* + +
+ +--- + +## Descripción General + +**Zen C** es un lenguaje de programación de sistemas moderno que se compila a `GNU C`/`C11` legible por humanos. Proporciona un conjunto rico de características que incluyen inferencia de tipos, coincidencia de patrones (pattern matching), genéricos, traits, async/await y gestión manual de memoria con capacidades RAII, todo manteniendo una compatibilidad total con el ABI de C. + +## Comunidad + +¡Únete a la discusión, comparte demos, haz preguntas o reporta errores en el servidor oficial de Discord de Zen C! + +- Discord: [Únete aquí](https://discord.com/invite/q6wEsCmkJP) + +--- + +## Índice + +- [Descripción General](#descripción-general) +- [Comunidad](#comunidad) +- [Inicio Rápido](#inicio-rápido) + - [Instalación](#instalación) + - [Uso](#uso) + - [Variables de Entorno](#variables-de-entorno) +- [Referencia del Lenguaje](#referencia-del-lenguaje) + - [1. Variables y Constantes](#1-variables-y-constantes) + - [2. Tipos Primitivos](#2-tipos-primitivos) + - [3. Tipos Agregados](#3-tipos-agregados) + - [Arrays](#arrays) + - [Tuplas](#tuplas) + - [Structs](#structs) + - [Structs Opacos](#structs-opacos) + - [Enums](#enums) + - [Uniones](#uniones) + - [Alias de Tipos](#alias-de-tipos) + - [Alias de Tipos Opacos](#alias-de-tipos-opacos) + - [4. Funciones y Lambdas](#4-funciones-y-lambdas) + - [Funciones](#funciones) + - [Argumentos Const](#argumentos-const) + - [Argumentos por Defecto](#argumentos-por-defecto) + - [Lambdas (Clausuras)](#lambdas-clausuras) + - [Punteros a Funciones Crudos](#punteros-a-funciones-crudos) + - [Funciones Variádicas](#funciones-variádicas) + - [5. Flujo de Control](#5-flujo-de-control) + - [Condicionales](#condicionales) + - [Coincidencia de Patrones](#coincidencia-de-patrones) + - [Bucles](#bucles) + - [Control Avanzado](#control-avanzado) + - [6. Operadores](#6-operadores) + - [Operadores Sobrecargables](#operadores-sobrecargables) + - [Azúcar Sintáctico](#azúcar-sintáctico) + - [7. Impresión e Interpolación de Cadenas](#7-impresión-e-interpolación-de-cadenas) + - [Palabras Clave](#palabras-clave) + - [Abreviaturas](#abreviaturas) + - [Interpolación de Cadenas (F-strings)](#interpolación-de-cadenas-f-strings) + - [Prompts de Entrada (`?`)](#prompts-de-entrada-) + - [8. Gestión de Memoria](#8-gestión-de-memoria) + - [Defer](#defer) + - [Autofree](#autofree) + - [Semántica de Recursos (Movimiento por Defecto)](#semántica-de-recursos-movimiento-por-defecto) + - [RAII / Drop Trait](#raii--drop-trait) + - [9. Programación Orientada a Objetos](#9-programación-orientada-a-objetos) + - [Métodos](#métodos) + - [Traits](#traits) + - [Traits Estándar](#traits-estándar) + - [Composición](#composición) + - [10. Genéricos](#10-genéricos) + - [11. Concurrencia (Async/Await)](#11-concurrencia-asyncawait) + - [12. Metaprogramación](#12-metaprogramación) + - [Comptime](#comptime) + - [Embed](#embed) + - [Plugins](#plugins) + - [Macros de C Genéricas](#macros-de-c-genéricas) + - [13. Atributos](#13-atributos) + - [Atributos Personalizados](#atributos-personalizados) + - [Derivaciones Inteligentes](#derivaciones-inteligentes) + - [14. Ensamblador Inline](#14-ensamblador-inline) + - [Uso Básico](#uso-básico) + - [Volatile](#volatile) + - [Restricciones con Nombre](#restricciones-con-nombre) + - [15. Directivas de Construcción](#15-directivas-de-construcción) + - [16. Palabras Clave](#16-palabras-clave) +- [Biblioteca Estándar](#biblioteca-estándar) +- [Herramientas](#herramientas) + - [Servidor de Lenguaje (LSP)](#servidor-de-lenguaje-lsp) + - [REPL](#repl) +- [Soporte del Compilador y Compatibilidad](#soporte-del-compilador-y-compatibilidad) + - [Estado de la Suite de Pruebas](#estado-de-la-suite-de-pruebas) + - [Construyendo con Zig](#construyendo-con-zig) + - [Interop con C++](#interop-con-c) + - [Interop con CUDA](#interop-con-cuda) + - [Interop con Objective-C](#interop-con-objective-c) +- [Contribuyendo](#contribuyendo) +- [Atribuciones](#atribuciones) + +--- + +## Inicio Rápido + +### Instalación + +```bash +git clone https://github.com/z-libs/Zen-C.git +cd Zen-C +make +sudo make install +``` + +### Construcción Portable (APE) + +Zen C puede compilarse como un **Ejecutable Realmente Portable (APE)** usando [Cosmopolitan Libc](https://github.com/jart/cosmopolitan). Esto produce un único binario (`.com`) que se ejecuta de forma nativa en Linux, macOS, Windows, FreeBSD, OpenBSD y NetBSD en arquitecturas x86_64 y aarch64. + +**Prerrequisitos:** +- Toolchain `cosmocc` (debe estar en tu PATH) + +**Construcción e Instalación:** +```bash +make ape +sudo env "PATH=$PATH" make install-ape +``` + +**Artefactos:** +- `out/bin/zc.com`: El compilador Zen-C portable. Incluye la biblioteca estándar embebida dentro del ejecutable. +- `out/bin/zc-boot.com`: Un instalador bootstrap autónomo para configurar nuevos proyectos Zen-C. + +**Uso:** +```bash +# Ejecutar en cualquier SO compatible +./out/bin/zc.com build hello.zc -o hello +``` + +### Uso + +```bash +# Compilar y ejecutar +zc run hello.zc + +# Construir ejecutable +zc build hello.zc -o hello + +# Shell Interactiva +zc repl +``` + +### Variables de Entorno + +Puedes configurar `ZC_ROOT` para especificar la ubicación de la Biblioteca Estándar (importaciones estándar como `import "std/vector.zc"`). Esto te permite ejecutar `zc` desde cualquier directorio. + +```bash +export ZC_ROOT=/ruta/a/Zen-C +``` + +--- + +## Referencia del Lenguaje + +### 1. Variables y Constantes + +Zen C distingue entre constantes en tiempo de compilación y variables en tiempo de ejecución. + +#### Constantes Manifiestas (`def`) +Valores que existen solo en tiempo de compilación (se pliegan en el código). Úsalos para tamaños de arrays, configuración fija y números mágicos. + +```zc +def MAX_SIZE = 1024; +let buffer: char[MAX_SIZE]; // Tamaño de array válido +``` + +#### Variables (`let`) +Ubicaciones de almacenamiento en memoria. Pueden ser mutables o de solo lectura (`const`). + +```zc +let x = 10; // Mutable +x = 20; // OK + +let y: const int = 10; // Solo lectura (Calificado por tipo) +// y = 20; // Error: no se puede asignar a una constante +``` + +### 2. Tipos Primitivos + +| Tipo | Equivalente en C | Descripción | +|:---|:---|:---| +| `int`, `uint` | `int`, `unsigned int` | Entero estándar de la plataforma | +| `I8` .. `I128` o `i8` .. `i128` | `int8_t` .. `__int128_t` | Enteros con signo de ancho fijo | +| `U8` .. `U128` o `u8` .. `u128` | `uint8_t` .. `__uint128_t` | Enteros sin signo de ancho fijo | +| `isize`, `usize` | `ptrdiff_t`, `size_t` | Enteros del tamaño de un puntero | +| `byte` | `uint8_t` | Alias para U8 | +| `F32`, `F64` o `f32`, `f64` | `float`, `double` | Números de coma flotante | +| `bool` | `bool` | `true` o `false` | +| `char` | `char` | Carácter único | +| `string` | `char*` | Cadena de C (terminada en null) | +| `U0`, `u0`, `void` | `void` | Tipo vacío | + +### 3. Tipos Agregados + +#### Arrays +Arrays de tamaño fijo con semántica de valor. +```zc +def SIZE = 5; +let ints: int[SIZE] = [1, 2, 3, 4, 5]; +let zeros: [int; SIZE]; // Inicializado a cero +``` + +#### Tuplas +Agrupa múltiples valores, accede a los elementos por índice. +```zc +let pair = (1, "Hola"); +let x = pair.0; // 1 +let s = pair.1; // "Hola" +``` + +**Múltiples Valores de Retorno** + +Las funciones pueden retornar tuplas para proporcionar múltiples resultados: +```zc +fn sumar_y_restar(a: int, b: int) -> (int, int) { + return (a + b, a - b); +} + +let resultado = sumar_y_restar(3, 2); +let suma = resultado.0; // 5 +let resta = resultado.1; // 1 +``` + +**Desestructuración** + +Las tuplas pueden desestructurarse directamente en variables: +```zc +let (suma, resta) = sumar_y_restar(3, 2); +// suma = 5, resta = 1 +``` + +#### Structs +Estructuras de datos con campos de bits opcionales. +```zc +struct Point { + x: int; + y: int; +} + +// Inicialización de struct +let p = Point { x: 10, y: 20 }; + +// Campos de bits +struct Flags { + valid: U8 : 1; + mode: U8 : 3; +} +``` + +> **Nota**: Los structs usan [Semántica de Movimiento](#semántica-de-recursos-movimiento-por-defecto) por defecto. Los campos se pueden acceder mediante `.` incluso en punteros (Auto-Dereferencia). + +#### Structs Opacos +Puedes definir un struct como `opaque` para restringir el acceso a sus campos solo al módulo que lo define, permitiendo aún que el struct sea asignado en el stack (el tamaño es conocido). + +```zc +// En user.zc +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // OK: Dentro del módulo +} + +// En main.zc +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // Error: No se puede acceder al campo privado 'id' +} +``` + +#### Enums +Uniones etiquetadas (Tipos suma) capaces de contener datos. +```zc +enum Shape { + Circle(float), // Contiene el radio + Rect(float, float), // Contiene ancho y alto + Point // Sin datos +} +``` + +#### Uniones +Uniones estándar de C (acceso inseguro). +```zc +union Data { + i: int; + f: float; +} +``` + +#### Alias de Tipos +Crea un nuevo nombre para un tipo existente. +```zc +alias ID = int; +alias PointMap = Map; +``` + +#### Alias de Tipos Opacos +Puedes definir un alias de tipo como `opaque` para crear un nuevo tipo que sea distinto de su tipo subyacente fuera del módulo que lo define. Esto proporciona una fuerte encapsulación y seguridad de tipos sin la sobrecarga en tiempo de ejecución de un struct envoltorio. + +```zc +// En library.zc +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // Conversión implícita permitida dentro del módulo +} + +// En main.zc +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // Error: Falló la validación de tipos + // let h2: Handle = 10; // Error: Falló la validación de tipos +} +``` + +### 4. Funciones y Lambdas + +#### Funciones +```zc +fn suma(a: int, b: int) -> int { + return a + b; +} + +// Argumentos con nombre soportados en las llamadas +suma(a: 10, b: 20); +``` + +> **Nota**: Los argumentos con nombre deben seguir estrictamente el orden de los parámetros definidos. `suma(b: 20, a: 10)` es inválido. + +#### Argumentos Const +Los argumentos de las funciones pueden marcarse como `const` para imponer una semántica de solo lectura. Este es un calificador de tipo, no una constante manifiesta. + +```zc +fn print_val(v: const int) { + // v = 10; // Error: No se puede asignar a una variable const + println "{v}"; +} +``` + +#### Argumentos por Defecto +Las funciones pueden definir valores por defecto para los argumentos finales. Estos pueden ser literales, expresiones o código válido de Zen C (como constructores de structs). +```zc +// Valor por defecto simple +fn incrementar(val: int, cantidad: int = 1) -> int { + return val + cantidad; +} + +// Valor por defecto por expresión (evaluado en el sitio de la llamada) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// Valor por defecto de tipo struct +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "Modo Debug"; } +} + +fn main() { + incrementar(10); // 11 + offset(5); // 25 + init(); // Imprime "Modo Debug" +} +``` + +#### Lambdas (Clausuras) +Funciones anónimas que pueden capturar su entorno. +```zc +let factor = 2; +let doble = x -> x * factor; // Sintaxis de flecha +let completo = fn(x: int) -> int { return x * factor; }; // Sintaxis de bloque +``` + +#### Punteros a Funciones Crudos +Zen C soporta punteros a funciones de C crudos usando la sintaxis `fn*`. Esto permite una interoperabilidad perfecta con bibliotecas de C que esperan punteros a funciones sin la sobrecarga de las clausuras. + +```zc +// Función que recibe un puntero a función crudo +fn set_callback(cb: fn*(int)) { + cb(42); +} + +// Función que retorna un puntero a función crudo +fn get_callback() -> fn*(int) { + return mi_manejador; +} + +// Se soportan punteros a punteros de funciones (fn**) +let pptr: fn**(int) = &ptr; +``` + +#### Funciones Variádicas +Las funciones pueden aceptar un número variable de argumentos usando `...` y el tipo `va_list`. +```zc +fn log(lvl: int, fmt: char*, ...) { + let ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // Usa stdio de C + va_end(ap); +} +``` + +### 5. Flujo de Control + +#### Condicionales +```zc +if x > 10 { + print("Grande"); +} else if x > 5 { + print("Mediano"); +} else { + print("Pequeño"); +} + +// Ternario +let y = x > 10 ? 1 : 0; +``` + +#### Coincidencia de Patrones (Pattern Matching) +Una alternativa potente al `switch`. +```zc +match val { + 1 => { print "Uno" }, + 2 || 3 => { print "Dos o Tres" }, // OR con || + 4 or 5 => { print "Cuatro o Cinco" }, // OR con 'or' + 6, 7, 8 => { print "Seis a Ocho" }, // OR con coma + 10 .. 15 => { print "10 a 14" }, // Rango exclusivo (Legado) + 10 ..< 15 => { print "10 a 14" }, // Rango exclusivo (Explícito) + 20 ..= 25 => { print "20 a 25" }, // Rango inclusivo + _ => { print "Otro" }, +} + +// Desestructuración de Enums +match shape { + Shape::Circle(r) => println "Radio: {r}", + Shape::Rect(w, h) => println "Área: {w*h}", + Shape::Point => println "Punto" +} +``` + +#### Vinculación por Referencia (Reference Binding) +Para inspeccionar un valor sin tomar posesión de él (sin moverlo), usa la palabra clave `ref` en el patrón. Esto es esencial para tipos que implementan la Semántica de Movimiento (como `Option`, `Result`, structs que no son Copy). + +```zc +let opt = Some(ValorNoCopy{...}); +match opt { + Some(ref x) => { + // 'x' es un puntero al valor dentro de 'opt' + // 'opt' NO se mueve ni se consume aquí + println "{x.field}"; + }, + None => {} +} +``` + +#### Bucles +```zc +// Rango +for i in 0..10 { ... } // Exclusivo (0 al 9) +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 { ... } + +// While +while x < 10 { ... } + +// Infinito con etiqueta +externo: loop { + if terminado { break externo; } +} + +// Repetir N veces +for _ in 0..5 { ... } +``` + +#### Control Avanzado +```zc +// Guard: Ejecuta else y retorna si la condición es falsa +guard ptr != NULL else { return; } + +// Unless: Si no es verdadero +unless es_valido { return; } +``` + +### 6. Operadores + +Zen C soporta la sobrecarga de operadores para structs definidos por el usuario implementando nombres de métodos específicos. + +#### Operadores Sobrecargables + +| Categoría | Operador | Nombre del Método | +|:---|:---|:---| +| **Aritméticos** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **Comparación** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **Bitwise** | `&`, `|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **Unarios** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **Índice** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +> **Nota sobre la igualdad de cadenas**: +> - `string == string` realiza una **comparación de valores** (equivalente a `strcmp`). +> - `char* == char*` realiza una **comparación de punteros** (comprueba direcciones de memoria). +> - Comparaciones mixtas (ej. `string == char*`) por defecto realizan una **comparación de punteros**. + +**Ejemplo:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +let p3 = p1 + p2; // Llama a p1.add(p2) +``` + +#### Azúcar Sintáctico + +Estos operadores son características integradas del lenguaje y no pueden sobrecargarse directamente. + +| Operador | Nombre | Descripción | +|:---|:---|:---| +| `|>` | Pipeline | `x |> f(y)` se desazucara a `f(x, y)` | +| `??` | Null Coalescing | `val ?? default` retorna `default` si `val` es NULL (punteros) | +| `??=` | Null Assignment | `val ??= init` asigna si `val` es NULL | +| `?.` | Navegación Segura | `ptr?.campo` accede al campo solo si `ptr` no es NULL | +| `?` | Operador Try | `res?` retorna el error si está presente (tipos Result/Option) | + +**Auto-Dereferencia**: +El acceso a campos por puntero (`ptr.campo`) y las llamadas a métodos (`ptr.metodo()`) dereferencian automáticamente el puntero, equivalente a `(*ptr).campo`. + +### 7. Impresión e Interpolación de Cadenas + +Zen C proporciona opciones versátiles para imprimir en la consola, incluyendo palabras clave y abreviaturas concisas. + +#### Palabras Clave + +- `print "texto"`: Imprime en `stdout` sin un salto de línea al final. +- `println "texto"`: Imprime en `stdout` con un salto de línea al final. +- `eprint "texto"`: Imprime en `stderr` sin un salto de línea al final. +- `eprintln "texto"`: Imprime en `stderr` con un salto de línea al final. + +#### Abreviaturas + +Zen C permite usar literales de cadena directamente como sentencias para una impresión rápida: + +- `"Hola Mundo"`: Equivalente a `println "Hola Mundo"`. (Añade salto de línea implícito) +- `"Hola Mundo"..`: Equivalente a `print "Hola Mundo"`. (Sin salto de línea final) +- `!"Error"`: Equivalente a `eprintln "Error"`. (Salida a stderr) +- `!"Error"..`: Equivalente a `eprint "Error"`. (Salida a stderr, sin salto de línea) + +#### Interpolación de Cadenas (F-strings) + +Puedes embeber expresiones directamente dentro de literales de cadena usando la sintaxis `{}`. Esto funciona con todos los métodos de impresión y abreviaturas de cadena. + +```zc +let x = 42; +let nombre = "Zen"; +println "Valor: {x}, Nombre: {nombre}"; +"Valor: {x}, Nombre: {nombre}"; // abreviatura println +``` + +#### Prompts de Entrada (`?`) + +Zen C soporta una abreviatura para solicitar entrada al usuario usando el prefijo `?`. + +- `? "Texto del prompt"`: Imprime el prompt (sin salto de línea) y espera la entrada (lee una línea). +- `? "Ingresa la edad: " (edad)`: Imprime el prompt y escanea la entrada en la variable `edad`. + - Los especificadores de formato se infieren automáticamente según el tipo de variable. + +```c +let edad: int; +? "¿Cuántos años tienes? " (edad); +println "Tienes {edad} años."; +``` + +### 8. Gestión de Memoria + +Zen C permite la gestión manual de memoria con ayudas ergonómicas. + +#### Defer +Ejecuta código cuando el ámbito actual finaliza. Las sentencias defer se ejecutan en orden LIFO (último en entrar, primero en salir). +```zc +let f = fopen("archivo.txt", "r"); +defer fclose(f); +``` + +> Para prevenir comportamientos indefinidos, las sentencias de flujo de control (`return`, `break`, `continue`, `goto`) **no están permitidas** dentro de un bloque `defer`. + +#### Autofree +Libera automáticamente la variable cuando finaliza el ámbito. +```zc +autofree let tipos = malloc(1024); +``` + +#### Semántica de Recursos (Movimiento por Defecto) +Zen C trata los tipos con destructores (como `File`, `Vec` o punteros de malloc) como **Recursos**. Para prevenir errores de doble liberación (double-free), los recursos no pueden duplicarse implícitamente. + +- **Movimiento por Defecto**: La asignación de una variable de recurso transfiere la posesión. La variable original se vuelve inválida (Movida). +- **Tipos Copy**: Los tipos sin destructores pueden optar por el comportamiento `Copy`, haciendo que la asignación sea una duplicación. + +**Diagnóstico y Filosofía**: +Si ves un error "Use of moved value", el compilador te está diciendo: *"Este tipo posee un recurso (como memoria o un manejador) y copiarlo a ciegas es inseguro."* + +> **Contraste:** A diferencia de C/C++, Zen C no duplica implícitamente los valores que poseen recursos. + +**Argumentos de Función**: +Pasar un valor a una función sigue las mismas reglas que la asignación: los recursos se mueven a menos que se pasen por referencia. + +```zc +fn procesar(r: Recurso) { ... } // 'r' se mueve dentro de la función +fn mirar(r: Recurso*) { ... } // 'r' es prestado (referencia) +``` + +**Clonación Explícita**: +Si *realmente* quieres dos copias de un recurso, hazlo explícito: + +```zc +let b = a.clone(); // Llama al método 'clone' del trait Clone +``` + +**Optar por Copy (Tipos de Valor)**: +Para tipos pequeños sin destructores: + +```zc +struct Point { x: int; y: int; } +impl Copy for Point {} // Optar por la duplicación implícita + +fn main() { + let p1 = Point { x: 1, y: 2 }; + let p2 = p1; // Copiado. p1 sigue siendo válido. +} +``` + +#### RAII / Drop Trait +Implementa `Drop` para ejecutar lógica de limpieza automáticamente. +```zc +impl Drop for MiEstructura { + fn drop(self) { + self.free(); + } +} +``` + +### 9. Programación Orientada a Objetos + +#### Métodos +Define métodos en los tipos usando `impl`. +```zc +impl Point { + // Método estático (convención de constructor) + fn new(x: int, y: int) -> Self { + return Point{x: x, y: y}; + } + + // Método de instancia + fn dist(self) -> float { + return sqrt(self.x * self.x + self.y * self.y); + } +} +``` + +#### Traits +Define un comportamiento compartido. +```zc +struct Circle { radio: f32; } + +trait Dibujable { + fn dibujar(self); +} + +impl Dibujable for Circle { + fn dibujar(self) { ... } +} + +let circulo = Circle{}; +let dibujable: Dibujable = &circulo; +``` + +#### Traits Estándar +Zen C incluye traits estándar que se integran con la sintaxis del lenguaje. + +**Iterable** + +Implementa `Iterable` para habilitar bucles `for-in` para tus tipos personalizados. + +```zc +import "std/iter.zc" + +// Define un Iterador +struct MiIter { + actual: int; + final: int; +} + +impl MiIter { + fn next(self) -> Option { + if self.actual < self.final { + self.actual += 1; + return Option::Some(self.actual - 1); + } + return Option::None(); + } +} + +// Implementa Iterable +impl MiRango { + fn iterator(self) -> MiIter { + return MiIter{actual: self.inicio, final: self.fin}; + } +} + +// Uso en un Bucle +for i in mi_rango { + println "{i}"; +} +``` + +**Drop** + +Implementa `Drop` para definir un destructor que se ejecuta cuando el objeto sale de ámbito (RAII). + +```zc +import "std/mem.zc" + +struct Recurso { + ptr: void*; +} + +impl Drop for Recurso { + fn drop(self) { + if self.ptr != NULL { + free(self.ptr); + } + } +} +``` + +> **Nota:** Si una variable es movida, no se llama a `drop` en la variable original. Se adhiere a la [Semántica de Recursos](#semántica-de-recursos-movimiento-por-defecto). + +**Copy** + +Trait marcador para optar por el comportamiento `Copy` (duplicación implícita) en lugar de la semántica de movimiento. Se usa mediante `@derive(Copy)`. + +> **Regla:** Los tipos que implementan `Copy` no deben definir un destructor (`Drop`). + +```zc +@derive(Copy) +struct Point { x: int; y: int; } + +fn main() { + let p1 = Point{x: 1, y: 2}; + let p2 = p1; // ¡Copiado! p1 sigue siendo válido. +} +``` + +**Clone** + +Implementa `Clone` para permitir la duplicación explícita de tipos que poseen recursos. + +```zc +import "std/mem.zc" + +struct MiBox { val: int; } + +impl Clone for MiBox { + fn clone(self) -> MiBox { + return MiBox{val: self.val}; + } +} + +fn main() { + let b1 = MiBox{val: 42}; + let b2 = b1.clone(); // Copia explícita +} +``` + +#### Composición +Usa `use` para embeber otros structs. Puedes mezclarlos (aplanar campos) o nombrarlos (anidar campos). + +```zc +struct Entity { id: int; } + +struct Player { + // Mezcla (Mixin - Sin nombre): Aplana los campos + use Entity; // Añade 'id' a Player directamente + nombre: string; +} + +struct Match { + // Composición (Con nombre): Anida los campos + use p1: Player; // Accedido mediante match.p1 + use p2: Player; // Accedido mediante match.p2 +} +``` + +### 10. Genéricos + +Plantillas seguras para tipos para Structs y Funciones. + +```zc +// Struct Genérico +struct Box { + item: T; +} + +// Función Genérica +fn identidad(val: T) -> T { + return val; +} + +// Genéricos con múltiples parámetros +struct Par { + llave: K; + valor: V; +} +``` + +### 11. Concurrencia (Async/Await) + +Construido sobre pthreads. + +```zc +async fn obtener_datos() -> string { + // Se ejecuta en segundo plano + return "Datos"; +} + +fn main() { + let futuro = obtener_datos(); + let resultado = await futuro; +} +``` + +### 12. Metaprogramación + +#### Comptime +Ejecuta código en tiempo de compilación para generar código fuente o imprimir mensajes. +```zc +comptime { + // Genera código en tiempo de compilación (escrito en stdout) + println "let fecha_compilacion = \"2024-01-01\";"; +} + +println "Fecha de compilación: {fecha_compilacion}"; +``` + +#### Embed +Embebe archivos como los tipos especificados. +```zc +// Por defecto (Slice_char) +let datos = embed "assets/logo.png"; + +// Embed tipado +let texto = embed "shader.glsl" as string; // Embebe como C-string +let rom = embed "bios.bin" as u8[1024]; // Embebe como array fijo +let wav = embed "sound.wav" as u8[]; // Embebe como Slice_u8 +``` + +#### Plugins +Importa plugins del compilador para extender la sintaxis. +```zc +import plugin "regex" +let re = regex! { ^[a-z]+$ }; +``` + +#### Macros de C Genéricas +Pasa macros del preprocesador directamente a C. + +> **Consejo**: Para constantes simples, usa `def` en su lugar. Usa `#define` cuando necesites macros del preprocesador de C o flags de compilación condicional. + +```zc +#define MAX_BUFFER 1024 +``` + +### 13. Atributos + +Decora funciones y structs para modificar el comportamiento del compilador. + +| Atributo | Ámbito | Descripción | +|:---|:---|:---| +| `@must_use` | Fn | Advierte si el valor de retorno es ignorado. | +| `@deprecated("msg")` | Fn/Struct | Advierte sobre el uso con un mensaje. | +| `@inline` | Fn | Sugiere al compilador hacer inlininig. | +| `@noinline` | Fn | Previene el inlining. | +| `@packed` | Struct | Elimina el padding entre campos. | +| `@align(N)` | Struct | Fuerza el alineamiento a N bytes. | +| `@constructor` | Fn | Se ejecuta antes de main. | +| `@destructor` | Fn | Se ejecuta después de que main termine. | +| `@unused` | Fn/Var | Suprime advertencias de variables no usadas. | +| `@weak` | Fn | Enlace de símbolo débil (weak symbol linkage). | +| `@section("nombre")` | Fn | Coloca el código en una sección específica. | +| `@noreturn` | Fn | La función no retorna (ej. exit). | +| `@pure` | Fn | La función no tiene efectos secundarios (sugestión de optimización). | +| `@cold` | Fn | Es poco probable que la función se ejecute (sugestión de predicción de saltos). | +| `@hot` | Fn | La función se ejecuta frecuentemente (sugestión de optimización). | +| `@export` | Fn/Struct | Exporta el símbolo (visibilidad por defecto). | +| `@global` | Fn | CUDA: Punto de entrada del kernel (`__global__`). | +| `@device` | Fn | CUDA: Función de dispositivo (`__device__`). | +| `@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`. | +| `@` | Cualquier | Pasa atributos genéricos a C (ej. `@flatten`, `@alias("nombre")`). | + +#### Atributos Personalizados + +Zen C soporta un potente sistema de **Atributos Personalizados** que te permite usar cualquier `__attribute__` de GCC/Clang directamente en tu código. Cualquier atributo que no sea reconocido explícitamente por el compilador de Zen C es tratado como un atributo genérico y se pasa al código C generado. + +Esto proporciona acceso a características avanzadas del compilador, optimizaciones y directivas del enlazador sin necesidad de soporte explícito en el núcleo del lenguaje. + +#### Mapeo de Sintaxis +Los atributos de Zen C se mapean directamente a atributos de C: +- `@nombre` → `__attribute__((nombre))` +- `@nombre(args)` → `__attribute__((nombre(args)))` +- `@nombre("string")` → `__attribute__((nombre("string")))` + +#### Derivaciones Inteligentes + +Zen C proporciona "Derivaciones Inteligentes" que respetan la Semántica de Movimiento: + +- **`@derive(Eq)`**: Genera un método de igualdad que recibe los argumentos por referencia (`fn eq(self, other: T*)`). + - Al comparar dos structs que no son Copy (`a == b`), el compilador pasa automáticamente `b` por referencia (`&b`) para evitar moverlo. + - Las comprobaciones de igualdad recursivas en los campos también prefieren el acceso por puntero para prevenir la transferencia de posesión. + +### 14. Ensamblador Inline + +Zen C proporciona soporte de primera clase para ensamblador inline, transpilando directamente a `asm` extendido de estilo GCC. + +#### Uso Básico +Escribe ensamblador crudo dentro de bloques `asm`. Las cadenas se concatenan automáticamente. +```zc +asm { + "nop" + "mfence" +} +``` + +#### Volatile +Previene que el compilador optimice y elimine el ensamblador que tiene efectos secundarios. +```zc +asm volatile { + "rdtsc" +} +``` + +#### Restricciones con Nombre +Zen C simplifica la compleja sintaxis de restricciones de GCC con vinculaciones con nombre. + +```zc +// Sintaxis: : out(variable) : in(variable) : clobber(reg) +// Usa la sintaxis de marcador de posición {variable} para legibilidad + +fn sumar(a: int, b: int) -> int { + let resultado: int; + asm { + "add {resultado}, {a}, {b}" + : out(resultado) + : in(a), in(b) + : clobber("cc") + } + return resultado; +} +``` + +| Tipo | Sintaxis | Equivalente GCC | +|:---|:---|:---| +| **Salida** | `: out(variable)` | `"=r"(variable)` | +| **Entrada** | `: in(variable)` | `"r"(variable)` | +| **Clobber** | `: clobber("rax")` | `"rax"` | +| **Memoria** | `: clobber("memory")` | `"memory"` | + +> **Nota:** Cuando uses la sintaxis de Intel (mediante `-masm=intel`), debes asegurarte de que tu construcción esté configurada correctamente (por ejemplo, `//> cflags: -masm=intel`). TCC no soporta el ensamblador con sintaxis Intel. + +### 15. Directivas de Construcción + +Zen C soporta comentarios especiales en la parte superior de tu archivo fuente para configurar el proceso de construcción sin necesidad de un complejo sistema de construcción o Makefile. + +| Directiva | Argumentos | Descripción | +|:---|:---|:---| +| `//> link:` | `-lfoo` o `ruta/a/lib.a` | Enlaza contra una biblioteca o archivo objeto. | +| `//> lib:` | `ruta/a/libs` | Añade una ruta de búsqueda de biblioteca (`-L`). | +| `//> include:` | `ruta/a/headers` | Añade una ruta de búsqueda de cabeceras (`-I`). | +| `//> framework:` | `Cocoa` | Enlaza contra un framework de macOS. | +| `//> cflags:` | `-Wall -O3` | Pasa flags arbitrarios al compilador de C. | +| `//> define:` | `MACRO` o `LLAVE=VAL` | Define una macro del preprocesador (`-D`). | +| `//> pkg-config:` | `gtk+-3.0` | Ejecuta `pkg-config` y añade `--cflags` y `--libs`. | +| `//> shell:` | `comando` | Ejecuta un comando de shell durante la construcción. | +| `//> get:` | `http://url/archivo` | Descarga un archivo si el archivo específico no existe. | + +#### Características + +**1. Protección de SO (OS Guarding)** +Prefija las directivas con el nombre de un SO para aplicarlas solo en plataformas específicas. +Prefijos soportados: `linux:`, `windows:`, `macos:` (o `darwin:`). + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. Expansión de Variables de Entorno** +Usa la sintaxis `${VAR}` para expandir variables de entorno en tus directivas. + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + +#### Ejemplos + +```zc +//> include: ./include +//> lib: ./libs +//> link: -lraylib -lm +//> cflags: -Ofast +//> pkg-config: gtk+-3.0 + +import "raylib.h" + +fn main() { ... } +``` + +### 16. Palabras Clave + +Las siguientes palabras clave están reservadas en Zen C. + +#### Declaraciones +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### Flujo de Control +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### Especiales +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### Constantes +`true`, `false`, `null` + +#### Reservadas de C +Los siguientes identificadores están reservados porque son palabras clave en 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` + +#### Operadores +`and`, `or` + +--- + +## Biblioteca Estándar + +Zen C incluye una biblioteca estándar (`std`) que cubre las funcionalidades esenciales. + +[Explorar la Documentación de la Biblioteca Estándar](docs/std/README.md) + +### Módulos Clave + +| Módulo | Descripción | Docs | +| :--- | :--- | :--- | +| **`std/vec.zc`** | Array dinámico creíble `Vec`. | [Docs](docs/std/vec.md) | +| **`std/string.zc`** | Tipo `String` asignado en el heap con soporte UTF-8. | [Docs](docs/std/string.md) | +| **`std/queue.zc`** | Cola FIFO (Ring Buffer). | [Docs](docs/std/queue.md) | +| **`std/map.zc`** | Mapa Hash Genérico `Map`. | [Docs](docs/std/map.md) | +| **`std/fs.zc`** | Operaciones del sistema de archivos. | [Docs](docs/std/fs.md) | +| **`std/io.zc`** | Entrada/Salida estándar (`print`/`println`). | [Docs](docs/std/io.md) | +| **`std/option.zc`** | Valores opcionales (`Some`/`None`). | [Docs](docs/std/option.md) | +| **`std/result.zc`** | Gestión de errores (`Ok`/`Err`). | [Docs](docs/std/result.md) | +| **`std/path.zc`** | Manipulación de rutas multiplataforma. | [Docs](docs/std/path.md) | +| **`std/env.zc`** | Variables de entorno del proceso. | [Docs](docs/std/env.md) | +| **`std/net.zc`** | Redes TCP (Sockets). | [Docs](docs/std/net.md) | +| **`std/thread.zc`** | Hilos y Sincronización. | [Docs](docs/std/thread.md) | +| **`std/time.zc`** | Medición de tiempo y espera (sleep). | [Docs](docs/std/time.md) | +| **`std/json.zc`** | Parseo y serialización de JSON. | [Docs](docs/std/json.md) | +| **`std/stack.zc`** | Pila LIFO `Stack`. | [Docs](docs/std/stack.md) | +| **`std/set.zc`** | Conjunto Hash Genérico `Set`. | [Docs](docs/std/set.md) | + +--- + +## Herramientas + +Zen C proporciona un Servidor de Lenguaje y un REPL integrados para mejorar la experiencia de desarrollo. + +### Servidor de Lenguaje (LSP) + +El Servidor de Lenguaje de Zen C (LSP) soporta las características estándar de LSP para integración con editores, proporcionando: + +* **Ir a la Definición** +* **Encontrar Referencias** +* **Información al pasar el ratón (Hover)** +* **Autocompletado** (Nombres de funciones/structs, autocompletado tras punto para métodos/campos) +* **Símbolos del Documento** (Esquema) +* **Ayuda de Firma** +* **Diagnósticos** (Errores sintácticos/semánticos) + +Para iniciar el servidor de lenguaje (normalmente configurado en los ajustes de LSP de tu editor): + +```bash +zc lsp +``` + +Se comunica mediante I/O estándar (JSON-RPC 2.0). + +### REPL + +El bucle Read-Eval-Print te permite experimentar con el código de Zen C de forma interactiva. + +```bash +zc repl +``` + +#### Características + +* **Codificación Interactiva**: Escribe expresiones o sentencias para su evaluación inmediata. +* **Historial Persistente**: Los comandos se guardan en `~/.zprep_history`. +* **Script de Inicio**: Carga automáticamente comandos desde `~/.zprep_init.zc`. + +#### Comandos + +| Comando | Descripción | +|:---|:---| +| `:help` | Muestra los comandos disponibles. | +| `:reset` | Limpia el historial de la sesión actual (variables/funciones). | +| `:vars` | Muestra las variables activas. | +| `:funcs` | Muestra las funciones definidas por el usuario. | +| `:structs` | Muestra los structs definidos por el usuario. | +| `:imports` | Muestra las importaciones activas. | +| `:history` | Muestra el historial de entrada de la sesión. | +| `:type ` | Muestra el tipo de una expresión. | +| `:c ` | Muestra el código C generado para una sentencia. | +| `:time ` | Benchmark de una expresión (ejecuta 1000 iteraciones). | +| `:edit [n]` | Edita el comando `n` (por defecto: el último) en `$EDITOR`. | +| `:save ` | Guarda la sesión actual en un archivo `.zc`. | +| `:load ` | Carga y ejecuta un archivo `.zc` en la sesión. | +| `:watch ` | Observa una expresión (se revalúa tras cada entrada). | +| `:unwatch ` | Elimina una observación. | +| `:undo` | Elimina el último comando de la sesión. | +| `:delete ` | Elimina el comando en el índice `n`. | +| `:clear` | Limpia la pantalla. | +| `:quit` | Sale del REPL. | +| `! ` | Ejecuta un comando de shell (ej. `!ls`). | + +--- + + +## Soporte del Compilador y Compatibilidad + +Zen C está diseñado para funcionar con la mayoría de los compiladores C11. Algunas características dependen de extensiones de GNU C, pero estas suelen funcionar en otros compiladores. Usa la flag `--cc` para cambiar de backend. + +```bash +zc run app.zc --cc clang +zc run app.zc --cc zig +``` + +### Estado de la Suite de Pruebas + +| Compilador | Tasa de Acierto | Características Soportadas | Limitaciones Conocidas | +|:---|:---:|:---|:---| +| **GCC** | **100%** | Todas las Características | Ninguna. | +| **Clang** | **100%** | Todas las Características | Ninguna. | +| **Zig** | **100%** | Todas las Características | Ninguna. Usa `zig cc` como reemplazo directo del compilador C. | +| **TCC** | **~70%** | Sintaxis Básica, Genéricos, Traits | Sin `__auto_type`, Sin ASM Intel, Sin funciones anidadas. | + +> **Recomendación:** Usa **GCC**, **Clang** o **Zig** para construcciones de producción. TCC es excelente para el prototipado rápido debido a su velocidad de compilación, pero le faltan algunas extensiones de C avanzadas en las que confía Zen C para el soporte total de características. + +### Construyendo con Zig + +El comando `zig cc` de Zig proporciona un reemplazo directo para GCC/Clang con un excelente soporte de compilación cruzada (cross-compilation). Para usar Zig: + +```bash +# Compilar y ejecutar un programa Zen C con Zig +zc run app.zc --cc zig + +# Construir el propio compilador Zen C con Zig +make zig +``` + +### Interop con C++ + +Zen C puede generar código compatible con C++ con la flag `--cpp`, permitiendo una integración perfecta con bibliotecas de C++. + +```bash +# Compilación directa con g++ +zc app.zc --cpp + +# O transpilar para construcción manual +zc transpile app.zc --cpp +g++ out.c mi_lib_cpp.o -o app +``` + +#### Usando C++ en Zen C + +Incluye cabeceras de C++ y usa bloques `raw` para el código C++: + +```zc +include +include + +raw { + std::vector hacer_vec(int a, int b) { + return {a, b}; + } +} + +fn main() { + let v = hacer_vec(1, 2); + raw { std::cout << "Tamaño: " << v.size() << std::endl; } +} +``` + +> **Nota:** La flag `--cpp` cambia el backend a `g++` y emite código compatible con C++ (usa `auto` en lugar de `__auto_type`, sobrecarga de funciones en lugar de `_Generic`, y casts explícitos para `void*`). + +#### Interop con CUDA + +Zen C soporta la programación de GPU transpilando a **CUDA C++**. Esto te permite aprovechar las potentes características de C++ (plantillas, constexpr) dentro de tus kernels mientras mantienes la sintaxis ergonómica de Zen C. + +```bash +# Compilación directa con nvcc +zc run app.zc --cuda + +# O transpilar para construcción manual +zc transpile app.zc --cuda -o app.cu +nvcc app.cu -o app +``` + +#### Atributos Específicos de CUDA + +| Atributo | Equivalente CUDA | Descripción | +|:---|:---|:---| +| `@global` | `__global__` | Función de kernel (se ejecuta en GPU, se llama desde el host) | +| `@device` | `__device__` | Función de dispositivo (se ejecuta en GPU, se llama desde GPU) | +| `@host` | `__host__` | Función de host (explícitamente solo CPU) | + +#### Sintaxis de Lanzamiento de Kernel + +Zen C proporciona una sentencia `launch` limpia para invocar kernels de CUDA: + +```zc +launch nombre_del_kernel(args) with { + grid: num_bloques, + block: hilos_por_bloque, + shared_mem: 1024, // Opcional + stream: mi_stream // Opcional +}; +``` + +Esto se transpila a: `nombre_del_kernel<<>>(args);` + +#### Escribiendo Kernels de CUDA + +Usa la sintaxis de funciones de Zen C con `@global` y la sentencia `launch`: + +```zc +import "std/cuda.zc" + +@global +fn kernel_suma(a: float*, b: float*, c: float*, n: int) { + let i = thread_id(); + if i < n { + c[i] = a[i] + b[i]; + } +} + +fn main() { + def N = 1024; + let d_a = cuda_alloc(N); + let d_b = cuda_alloc(N); + let d_c = cuda_alloc(N); + defer cuda_free(d_a); + defer cuda_free(d_b); + defer cuda_free(d_c); + + // ... inicialización de datos ... + + launch kernel_suma(d_a, d_b, d_c, N) with { + grid: (N + 255) / 256, + block: 256 + }; + + cuda_sync(); +} +``` + +#### Biblioteca Estándar (`std/cuda.zc`) +Zen C proporciona una biblioteca estándar para operaciones comunes de CUDA para reducir los bloques `raw`: + +```zc +import "std/cuda.zc" + +// Gestión de memoria +let d_ptr = cuda_alloc(1024); +cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float)); +defer cuda_free(d_ptr); + +// Sincronización +cuda_sync(); + +// Indexación de hilos (usar dentro de kernels) +let i = thread_id(); // Índice global +let bid = block_id(); +let tid = local_id(); +``` + + +> **Nota:** La flag `--cuda` establece `nvcc` como el compilador e implica el modo `--cpp`. Requiere el NVIDIA CUDA Toolkit. + +### 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. + +```bash +# Compilar con clang (o gcc/gnustep) +zc app.zc --objc --cc clang +``` + +#### Usando Objective-C en Zen C + +Usa `include` para las cabeceras y bloques `raw` para la sintaxis de Objective-C (`@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(@"¡Hola desde Objective-C!"); + [pool drain]; + } + println "¡Zen C también funciona!"; +} +``` + +> **Nota:** La interpolación de cadenas de Zen C funciona con objetos de Objective-C (`id`) llamando a `debugDescription` o `description`. + +--- + +## Contribuyendo + +¡Damos la bienvenida a las contribuciones! Ya sea corrigiendo errores, añadiendo documentación o proponiendo nuevas características. + +### Cómo Contribuir +1. **Haz un Fork del Repositorio**: flujo de trabajo estándar de GitHub. +2. **Crea una Rama de Característica**: `git checkout -b feature/NuevaCosa`. +3. **Guías de Código**: + * Sigue el estilo de C existente. + * Asegúrate de que todas las pruebas pasen: `make test`. + * Añade nuevas pruebas para tu característica en `tests/`. +4. **Envía un Pull Request**: Describe tus cambios claramente. + +### Ejecutando Pruebas +La suite de pruebas es tu mejor amiga. + +```bash +# Ejecutar todas las pruebas (GCC) +make test + +# Ejecutar una prueba específica +./zc run tests/test_match.zc + +# Ejecutar con un compilador diferente +./tests/run_tests.sh --cc clang +./tests/run_tests.sh --cc zig +./tests/run_tests.sh --cc tcc +``` + +### Extendiendo el Compilador +* **Parser**: `src/parser/` - Parser de descenso recursivo. +* **Codegen**: `src/codegen/` - Lógica del transpilador (Zen C -> GNU C/C11). +* **Biblioteca Estándar**: `std/` - Escrita en el propio Zen C. + +--- + +## Atribuciones + +Este proyecto utiliza bibliotecas de terceros. Los textos completos de las licencias pueden encontrarse en el directorio `LICENSES/`. + +* **[cJSON](https://github.com/DaveGamble/cJSON)** (Licencia MIT): Usado para el parseo y generación de JSON en el Servidor de Lenguaje. +* **[zc-ape](https://github.com/OEvgeny/zc-ape)** (Licencia MIT): El port original de Ejecutable Realmente Portable de Zen-C por [Eugene Olonov](https://github.com/OEvgeny). +* **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (Licencia ISC): La biblioteca fundamental que hace posible APE. diff --git a/README_ZH_CN.md b/README_ZH_CN.md new file mode 100644 index 0000000..52acb04 --- /dev/null +++ b/README_ZH_CN.md @@ -0,0 +1,1415 @@ + +
+ +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +
+ +
+ +# Zen C + +**现代开发体验。零开销。纯净 C。** + +[![构建状态](https://img.shields.io/badge/build-passing-brightgreen)]() +[![许可证](https://img.shields.io/badge/license-MIT-blue)]() +[![版本](https://img.shields.io/badge/version-0.1.0-orange)]() +[![平台](https://img.shields.io/badge/platform-linux-lightgrey)]() + +*像高级语言一样编写,像 C 一样运行。* + +
+ +--- + +## 概述 + +**Zen C** 是一种现代系统编程语言,可编译为人类可读的 `GNU C`/`C11`。它提供了一套丰富的特性,包括类型推断、模式匹配、泛型、Trait、async/await 以及具有 RAII 能力的手动内存管理,同时保持 100% 的 C ABI 兼容性。 + +## 社区 + +加入官方 Zen C Discord 服务器,参与讨论、展示 Demo、提问或报告 Bug! + +- Discord: [点击加入](https://discord.com/invite/q6wEsCmkJP) + +--- + +## 目录 + +- [概述](#概述) +- [社区](#社区) +- [快速入门](#快速入门) + - [安装](#安装) + - [用法](#用法) + - [环境变量](#环境变量) +- [语言参考](#语言参考) + - [1. 变量与常量](#1-变量与常量) + - [2. 原始类型](#2-原始类型) + - [3. 复合类型](#3-复合类型) + - [数组](#数组) + - [元组](#元组) + - [结构体](#结构体) + - [不透明结构体](#不透明结构体) + - [枚举](#枚举) + - [联合体](#联合体) + - [类型别名](#类型别名) + - [不透明类型别名](#不透明类型别名) + - [4. 函数与 Lambda](#4-函数与-lambda) + - [函数](#函数) + - [常量参数](#常量参数) + - [默认参数](#默认参数) + - [Lambda (闭包)](#lambda-闭包) + - [原始函数指针](#原始函数指针) + - [变参函数](#变参函数) + - [5. 控制流](#5-控制流) + - [条件语句](#条件语句) + - [模式匹配](#模式匹配) + - [循环](#循环) + - [高级控制](#高级控制) + - [6. 运算符](#6-运算符) + - [可重载运算符](#可重载运算符) + - [语法糖](#语法糖) + - [7. 打印与字符串插值](#7-打印与字符串插值) + - [关键字](#关键字) + - [简写形式](#简写形式) + - [字符串插值 (F-strings)](#字符串插值-f-strings) + - [输入提示 (`?`)](#输入提示-) + - [8. 内存管理](#8-内存管理) + - [Defer](#defer) + - [Autofree](#autofree) + - [资源语义 (默认移动)](#资源语义-默认移动) + - [RAII / Drop Trait](#raii--drop-trait) + - [9. 面向对象编程](#9-面向对象编程) + - [方法](#方法) + - [Trait](#trait) + - [标准 Trait](#标准-trait) + - [组合](#组合) + - [10. 泛型](#10-泛型) + - [11. 并发 (Async/Await)](#11-并发-asyncawait) + - [12. 元编程](#12-元编程) + - [Comptime](#comptime) + - [Embed](#embed) + - [插件](#插件) + - [泛型 C 宏](#泛型-c-宏) + - [13. 属性](#13-属性) + - [自定义属性](#自定义属性) + - [智能派生](#智能派生) + - [14. 内联汇编](#14-内联汇编) + - [基本用法](#基本用法) + - [Volatile](#volatile) + - [命名约束](#命名约束) + - [15. 构建指令](#15-构建指令) + - [16. 关键字](#16-关键字) +- [标准库](#标准库) +- [工具链](#工具链) + - [语言服务器 (LSP)](#语言服务器-lsp) + - [REPL](#repl) +- [编译器支持与兼容性](#编译器支持与兼容性) + - [测试套件状态](#测试套件状态) + - [使用 Zig 构建](#使用-zig-构建) + - [C++ 互操作](#c-互操作) + - [CUDA 互操作](#cuda-互操作) + - [Objective-C 互操作](#objective-c-互操作) +- [贡献](#贡献) +- [致谢与归属](#致谢与归属) + +--- + +## 快速入门 + +### 安装 + +```bash +git clone https://github.com/z-libs/Zen-C.git +cd Zen-C +make +sudo make install +``` + +### 便携式构建 (APE) + +Zen C 可以通过 [Cosmopolitan Libc](https://github.com/jart/cosmopolitan) 编译为 **Actually Portable Executable (APE)**。这将生成一个单个的可执行文件 (`.com`),能够原生运行在 Linux, macOS, Windows, FreeBSD, OpenBSD, 和 NetBSD 上的 x86_64 和 aarch64 架构上。 + +**前提条件:** +- `cosmocc` 工具链(必须在 PATH 中) + +**构建与安装:** +```bash +make ape +sudo env "PATH=$PATH" make install-ape +``` + +**产物:** +- `out/bin/zc.com`: 便携式 Zen-C 编译器。已将标准库嵌入到可执行文件中。 +- `out/bin/zc-boot.com`: 一个自包含的引导安装程序,用于设置新的 Zen-C 项目。 + +**用法:** +```bash +# 在任何支持的操作系统上运行 +./out/bin/zc.com build hello.zc -o hello +``` + +### 用法 + +```bash +# 编译并运行 +zc run hello.zc + +# 构建可执行文件 +zc build hello.zc -o hello + +# 交互式 Shell +zc repl +``` + +### 环境变量 + +你可以设置 `ZC_ROOT` 来指定标准库的位置(标准导入如 `import "std/vector.zc"`)。这允许你从任何目录运行 `zc`。 + +```bash +export ZC_ROOT=/path/to/Zen-C +``` + +--- + +## 语言参考 + +### 1. 变量与常量 + +Zen C 区分编译时常量和运行时变量。 + +#### 清单常量 (`def`) +仅在编译时存在的值(折叠到代码中)。用于数组大小、固定配置和魔术数字。 + +```zc +def MAX_SIZE = 1024; +let buffer: char[MAX_SIZE]; // 有效的数组大小 +``` + +#### 变量 (`let`) +内存中的存储位置。可以是可变的或只读的 (`const`)。 + +```zc +let x = 10; // 可变 +x = 20; // 允许 + +let y: const int = 10; // 只读 (类型修饰) +// y = 20; // 错误:无法赋值给 const 变量 +``` + +### 2. 原始类型 + +| 类型 | C 等效类型 | 描述 | +|:---|:---|:---| +| `int`, `uint` | `int`, `unsigned int` | 平台标准整数 | +| `I8` .. `I128` 或 `i8` .. `i128` | `int8_t` .. `__int128_t` | 有符号固定宽度整数 | +| `U8` .. `U128` 或 `u8` .. `u128` | `uint8_t` .. `__uint128_t` | 无符号固定宽度整数 | +| `isize`, `usize` | `ptrdiff_t`, `size_t` | 指针大小的整数 | +| `byte` | `uint8_t` | U8 的别名 | +| `F32`, `F64` 或 `f32`, `f64` | `float`, `double` | 浮点数 | +| `bool` | `bool` | `true` 或 `false` | +| `char` | `char` | 单个字符 | +| `string` | `char*` | C-string (以 null 结尾) | +| `U0`, `u0`, `void` | `void` | 空类型 | + +### 3. 复合类型 + +#### 数组 +具有值语义的固定大小数组。 +```zc +def SIZE = 5; +let ints: int[SIZE] = [1, 2, 3, 4, 5]; +let zeros: [int; SIZE]; // 零初始化的 +``` + +#### 元组 +将多个值组合在一起,通过索引访问元素。 +```zc +let pair = (1, "Hello"); +let x = pair.0; // 1 +let s = pair.1; // "Hello" +``` + +**多个返回值** + +函数可以返回元组以提供多个结果: +```zc +fn add_and_subtract(a: int, b: int) -> (int, int) { + return (a + b, a - b); +} + +let result = add_and_subtract(3, 2); +let sum = result.0; // 5 +let diff = result.1; // 1 +``` + +**解构** + +元组可以直接解构为多个变量: +```zc +let (sum, diff) = add_and_subtract(3, 2); +// sum = 5, diff = 1 +``` + +#### 结构体 +带有可选位域的数据结构。 +```zc +struct Point { + x: int; + y: int; +} + +// 结构体初始化 +let p = Point { x: 10, y: 20 }; + +// 位域 +struct Flags { + valid: U8 : 1; + mode: U8 : 3; +} +``` + +> **注意**:结构体默认使用 [移动语义](#资源语义-默认移动)。即使是指针,也可以通过 `.` 访问字段(自动解引用)。 + +#### 不透明结构体 +你可以将结构体定义为 `opaque`,以将对其字段的访问限制在定义该结构体的模块内部,同时仍允许在栈上分配该结构体(大小已知)。 + +```zc +// 在 user.zc 中 +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // 允许:在模块内部 +} + +// 在 main.zc 中 +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // 错误:无法访问私有字段 'id' +} +``` + +#### 枚举 +能够持有数据的标签联合 (Sum types)。 +```zc +enum Shape { + Circle(float), // 持有半径 + Rect(float, float), // 持有宽、高 + Point // 不带数据 +} +``` + +#### 联合体 +标准 C 联合体(不安全访问)。 +```zc +union Data { + i: int; + f: float; +} +``` + +#### 类型别名 +为现有类型创建新名称。 +```zc +alias ID = int; +alias PointMap = Map; +``` + +#### 不透明类型别名 +你可以将类型别名定义为 `opaque`,从而在定义模块之外创建一个与基础类型不同的新类型。这提供了强大的封装和类型安全性,而没有包装结构体的运行时开销。 + +```zc +// 在 library.zc 中 +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // 允许在模块内部进行隐式转换 +} + +// 在 main.zc 中 +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // 错误:类型验证失败 + // let h2: Handle = 10; // 错误:类型验证失败 +} +``` + +### 4. 函数与 Lambda + +#### 函数 +```zc +fn add(a: int, b: int) -> int { + return a + b; +} + +// 调用时支持命名参数 +add(a: 10, b: 20); +``` + +> **注意**:命名参数必须严格遵循定义的参数顺序。`add(b: 20, a: 10)` 是无效的。 + +#### 常量参数 +函数参数可以标记为 `const` 以强制执行只读语义。这是一个类型修饰符,而不是清单常量。 + +```zc +fn print_val(v: const int) { + // v = 10; // 错误:无法赋值给 const 变量 + println "{v}"; +} +``` + +#### 默认参数 +函数可以为尾部参数定义默认值。这些值可以是字面量、表达式或有效的 Zen C 代码(如结构体构造函数)。 +```zc +// 简单默认值 +fn increment(val: int, amount: int = 1) -> int { + return val + amount; +} + +// 表达式默认值(在调用处计算) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// 结构体默认值 +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "调试模式"; } +} + +fn main() { + increment(10); // 11 + offset(5); // 25 + init(); // 打印 "调试模式" +} +``` + +#### Lambda (闭包) +可以捕获环境的匿名函数。 +```zc +let factor = 2; +let double = x -> x * factor; // 箭头语法 +let full = fn(x: int) -> int { return x * factor; }; // 块语法 +``` + +#### 原始函数指针 +Zen C 使用 `fn*` 语法支持原始 C 函数指针。这允许与期望函数指针且没有闭包开销的 C 库进行无缝互操作。 + +```zc +// 接受原始函数指针的函数 +fn set_callback(cb: fn*(int)) { + cb(42); +} + +// 返回原始函数指针的函数 +fn get_callback() -> fn*(int) { + return my_handler; +} + +// 支持指向函数指针的指针 (fn**) +let pptr: fn**(int) = &ptr; +``` + +#### 变参函数 +函数可以使用 `...` 和 `va_list` 类型接受可变数量的参数。 +```zc +fn log(lvl: int, fmt: char*, ...) { + let ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // 使用 C stdio + va_end(ap); +} +``` + +### 5. 控制流 + +#### 条件语句 +```zc +if x > 10 { + print("Large"); +} else if x > 5 { + print("Medium"); +} else { + print("Small"); +} + +// 三元运算符 +let y = x > 10 ? 1 : 0; +``` + +#### 模式匹配 +`switch` 的强大替代方案。 +```zc +match val { + 1 => { print "One" }, + 2 || 3 => { print "Two or Three" }, // 使用 || 进行 或 操作 + 4 or 5 => { print "Four or Five" }, // 使用 'or' 进行 或 操作 + 6, 7, 8 => { print "Six to Eight" }, // 使用逗号进行 或 操作 + 10 .. 15 => { print "10 to 14" }, // 左闭右开区间 (旧语法) + 10 ..< 15 => { print "10 to 14" }, // 左闭右开区间 (显式) + 20 ..= 25 => { print "20 to 25" }, // 全闭区间 + _ => { print "Other" }, +} + +// 解构枚举 +match shape { + Shape::Circle(r) => println "半径: {r}", + Shape::Rect(w, h) => println "面积: {w*h}", + Shape::Point => println "点" +} +``` + +#### 引用绑定 +为了在不获取所有权(移动)的情况下检查一个值,在模式中使用 `ref` 关键字。这对于实现了移动语义的类型(如 `Option`, `Result`, 非 Copy 结构体)至关重要。 + +```zc +let opt = Some(NonCopyVal{...}); +match opt { + Some(ref x) => { + // 'x' 是指向 'opt' 内部值的指针 + // 'opt' 在此处不会被移动/消耗 + println "{x.field}"; + }, + None => {} +} +``` + +#### 循环 +```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 step 2 { ... } + +// 迭代器 (Vec, Array, 或自定义 Iterable) +for item in collection { ... } + +// While 循环 +while x < 10 { ... } + +// 带标签的无限循环 +outer: loop { + if done { break outer; } +} + +// 重复 N 次 +for _ in 0..5 { ... } +``` + +#### 高级控制 +```zc +// Guard: 如果条件为假,则执行 else 块并返回 +guard ptr != NULL else { return; } + +// Unless: 除非为真(即如果为假) +unless is_valid { return; } +``` + +### 6. 运算符 + +Zen C 通过实现特定的方法名来支持用户定义结构体的运算符重载。 + +#### 可重载运算符 + +| 类别 | 运算符 | 方法名 | +|:---|:---|:---| +| **算术** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **比较** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **位运算** | `&`, `|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **一元** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **索引** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +> **关于字符串相等性的说明**: +> - `string == string` 进行 **值比较**(等同于 `strcmp`)。 +> - `char* == char*` 进行 **指针比较**(检查内存地址)。 +> - 混合比较(例如 `string == char*`)默认为 **指针比较**。 + +**示例:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +let p3 = p1 + p2; // 调用 p1.add(p2) +``` + +#### 语法糖 + +这些运算符是内置语言特性,不能直接重载。 + +| 运算符 | 名称 | 描述 | +|:---|:---|:---| +| `|>` | 管道 | `x |> f(y)` 脱糖为 `f(x, y)` | +| `??` | 空合并 | 如果 `val` 为 NULL,`val ?? default` 返回 `default` (用于指针) | +| `??=` | 空赋值 | 如果 `val` 为 NULL 则赋值 | +| `?.` | 安全导航 | 仅当 `ptr` 不为 NULL 时访问字段 | +| `?` | Try 运算符 | 如果存在错误则返回 (用于 Result/Option 类型) | + +**自动解引用**: +指针字段访问 (`ptr.field`) 和方法调用 (`ptr.method()`) 会自动解引用指针,等同于 `(*ptr).field`。 + +### 7. 打印与字符串插值 + +Zen C 提供了多种控制台打印选项,包括关键字和简洁的简写形式。 + +#### 关键字 + +- `print "text"`: 打印到 `stdout`,不带尾随换行符。 +- `println "text"`: 打印到 `stdout`,带尾随换行符。 +- `eprint "text"`: 打印到 `stderr`,不带尾随换行符。 +- `eprintln "text"`: 打印到 `stderr`,带尾随换行符。 + +#### 简写形式 + +Zen C 允许直接将字符串字面量用作语句来进行快速打印: + +- `"Hello World"`: 等同于 `println "Hello World"`。(隐式添加换行符) +- `"Hello World"..`: 等同于 `print "Hello World"`。(不带尾随换行符) +- `!"Error"`: 等同于 `eprintln "Error"`。(输出到 stderr) +- `!"Error"..`: 等同于 `eprint "Error"`。(输出到 stderr,不带换行符) + +#### 字符串插值 (F-strings) + +你可以使用 `{}` 语法将表达式直接嵌入到字符串字面量中。这适用于所有打印方法和字符串简写。 + +```zc +let x = 42; +let name = "Zen"; +println "值: {x}, 名称: {name}"; +"值: {x}, 名称: {name}"; // 简写形式的 println +``` + +#### 输入提示 (`?`) + +Zen C 支持使用 `?` 前缀进行用户输入提示的简写。 + +- `? "提示文本"`: 打印提示信息(不换行)并等待输入(读取一行)。 +- `? "输入年龄: " (age)`: 打印提示并扫描输入到变量 `age` 中。 + - 格式说明符会根据变量类型自动推断。 + +```c +let age: int; +? "你多大了? " (age); +println "你 {age} 岁了。"; +``` + +### 8. 内存管理 + +Zen C 允许带有符合人体工程学辅助的手动内存管理。 + +#### Defer +在当前作用域退出时执行代码。Defer 语句按照后进先出 (LIFO) 的顺序执行。 +```zc +let f = fopen("file.txt", "r"); +defer fclose(f); +``` + +> 为了防止未定义行为,`defer` 块内不允许使用控制流语句(`return`, `break`, `continue`, `goto`)。 + +#### Autofree +在作用域退出时自动释放变量。 +```zc +autofree let types = malloc(1024); +``` + +#### 资源语义 (默认移动) +Zen C 将带有析构函数(如 `File`, `Vec`, 或 malloc 的指针)的类型视为 **资源**。为了防止双重释放错误,资源不能被隐式复制。 + +- **默认移动**:分配资源变量会转移所有权。原始变量变得无效(已移动)。 +- **复制类型**:没有析构函数的类型可以申请参与 `Copy` 行为,使赋值变成复制。 + +**诊断与哲学**: +如果你看到错误 "Use of moved value",编译器是在告诉你:*"此类型拥有一个资源(如内存或句柄),盲目复制它是不安全的。"* + +> **对比:** 与 C/C++ 不同,Zen C 不会隐式复制拥有资源的值。 + +**函数参数**: +将值传递给函数遵循与赋值相同的规则:资源会被移动,除非通过引用传递。 + +```zc +fn process(r: Resource) { ... } // 'r' 被移动进函数 +fn peek(r: Resource*) { ... } // 'r' 被借用 (引用) +``` + +**显式克隆**: +如果你 *确实* 想要一个资源的两个副本,请显式执行: + +```zc +let b = a.clone(); // 调用 Clone trait 中的 'clone' 方法 +``` + +**选择性复制 (值类型)**: +对于没有析构函数的小型类型: + +```zc +struct Point { x: int; y: int; } +impl Copy for Point {} // 选择参与隐式复制 + +fn main() { + let p1 = Point { x: 1, y: 2 }; + let p2 = p1; // 已复制。p1 保持有效。 +} +``` + +#### RAII / Drop Trait +实现 `Drop` 以自动运行清理逻辑。 +```zc +impl Drop for MyStruct { + fn drop(self) { + self.free(); + } +} +``` + +### 9. 面向对象编程 + +#### 方法 +使用 `impl` 为类型定义方法。 +```zc +impl Point { + // 静态方法 (构造函数惯例) + fn new(x: int, y: int) -> Self { + return Point{x: x, y: y}; + } + + // 实例方法 + fn dist(self) -> float { + return sqrt(self.x * self.x + self.y * self.y); + } +} +``` + +#### Trait +定义共享行为。 +```zc +struct Circle { radius: f32; } + +trait Drawable { + fn draw(self); +} + +impl Drawable for Circle { + fn draw(self) { ... } +} + +let circle = Circle{}; +let drawable: Drawable = &circle; +``` + +#### 标准 Trait +Zen C 包含与语言语法集成的标准 Trait。 + +**Iterable** + +实现 `Iterable` 以便为你的自定义类型启用 `for-in` 循环。 + +```zc +import "std/iter.zc" + +// 定义一个迭代器 +struct MyIter { + curr: int; + stop: int; +} + +impl MyIter { + fn next(self) -> Option { + if self.curr < self.stop { + self.curr += 1; + return Option::Some(self.curr - 1); + } + return Option::None(); + } +} + +// 实现 Iterable +impl MyRange { + fn iterator(self) -> MyIter { + return MyIter{curr: self.start, stop: self.end}; + } +} + +// 在循环中使用 +for i in my_range { + println "{i}"; +} +``` + +**Drop** + +实现 `Drop` 来定义一个在对象超出范围时运行的析构函数 (RAII)。 + +```zc +import "std/mem.zc" + +struct Resource { + ptr: void*; +} + +impl Drop for Resource { + fn drop(self) { + if self.ptr != NULL { + free(self.ptr); + } + } +} +``` + +> **注意:** 如果一个变量被移动,则原始变量不会调用 `drop`。它遵循 [资源语义](#资源语义-默认移动)。 + +**Copy** + +标记 Trait,用于选择支持 `Copy` 行为(隐式复制)而不是移动语义。通过 `@derive(Copy)` 使用。 + +> **规则:** 实现了 `Copy` 的类型不得定义析构函数 (`Drop`)。 + +```zc +@derive(Copy) +struct Point { x: int; y: int; } + +fn main() { + let p1 = Point{x: 1, y: 2}; + let p2 = p1; // 已复制!p1 保持有效。 +} +``` + +**Clone** + +实现 `Clone` 以允许显式复制拥有资源的类型。 + +```zc +import "std/mem.zc" + +struct MyBox { val: int; } + +impl Clone for MyBox { + fn clone(self) -> MyBox { + return MyBox{val: self.val}; + } +} + +fn main() { + let b1 = MyBox{val: 42}; + let b2 = b1.clone(); // 显式复制 +} +``` + +#### 组合 +使用 `use` 嵌入其他结构体。你可以将它们混合进来(展平字段)或者为它们命名(嵌套字段)。 + +```zc +struct Entity { id: int; } + +struct Player { + // 混入 (未命名): 展平字段 + use Entity; // 直接将 'id' 添加到 Player + name: string; +} + +struct Match { + // 组合 (命名): 嵌套字段 + use p1: Player; // 通过 match.p1 访问 + use p2: Player; // 通过 match.p2 访问 +} +``` + +### 10. 泛型 + +结构体和函数的类型安全模板。 + +```zc +// 泛型结构体 +struct Box { + item: T; +} + +// 泛型函数 +fn identity(val: T) -> T { + return val; +} + +// 多参数泛型 +struct Pair { + key: K; + value: V; +} +``` + +### 11. 并发 (Async/Await) + +基于 pthreads 构建。 + +```zc +async fn fetch_data() -> string { + // 在后台运行 + return "Data"; +} + +fn main() { + let future = fetch_data(); + let result = await future; +} +``` + +### 12. 元编程 + +#### Comptime +在编译时运行代码以生成源码或打印消息。 +```zc +comptime { + // 在编译时生成代码 (写入 stdout) + println "let build_date = \"2024-01-01\";"; +} + +println "构建日期: {build_date}"; +``` + +#### Embed +将文件嵌入为指定类型。 +```zc +// 默认 (Slice_char) +let data = embed "assets/logo.png"; + +// 类型化嵌入 +let text = embed "shader.glsl" as string; // 嵌入为 C-string +let rom = embed "bios.bin" as u8[1024]; // 嵌入为固定数组 +let wav = embed "sound.wav" as u8[]; // 嵌入为 Slice_u8 +``` + +#### 插件 +导入编译器插件以扩展语法。 +```zc +import plugin "regex" +let re = regex! { ^[a-z]+$ }; +``` + +#### 泛型 C 宏 +将预处理器宏传递给 C。 + +> **提示**:对于简单的常量,请使用 `def`。当你需要 C 预处理器宏或条件编译标志时,请使用 `#define`。 + +```zc +#define MAX_BUFFER 1024 +``` + +### 13. 属性 + +修饰函数和结构体以修改编译器行为。 + +| 属性 | 作用域 | 描述 | +|:---|:---|:---| +| `@must_use` | 函数 | 如果忽略返回值则发出警告。 | +| `@deprecated("msg")` | 函数/结构体 | 使用时发出带有消息的警告。 | +| `@inline` | 函数 | 提示编译器进行内联。 | +| `@noinline` | 函数 | 防止内联。 | +| `@packed` | 结构体 | 移除字段间的填充。 | +| `@align(N)` | 结构体 | 强制按 N 字节对齐。 | +| `@constructor` | 函数 | 在 main 之前运行。 | +| `@destructor` | 函数 | 在 main 退出后运行。 | +| `@unused` | 函数/变量 | 抑制未使用变量警告。 | +| `@weak` | 函数 | 弱符号链接。 | +| `@section("name")` | 函数 | 将代码放置在特定段中。 | +| `@noreturn` | 函数 | 函数不会返回 (例如 exit)。 | +| `@pure` | 函数 | 函数无副作用 (优化提示)。 | +| `@cold` | 函数 | 函数不太可能被执行 (分支预测提示)。 | +| `@hot` | 函数 | 函数频繁执行 (优化提示)。 | +| `@export` | 函数/结构体 | 导出符号 (默认可见性)。 | +| `@global` | 函数 | CUDA: 内核入口点 (`__global__`)。 | +| `@device` | 函数 | CUDA: 设备函数 (`__device__`)。 | +| `@host` | 函数 | CUDA: 主机函数 (`__host__`)。 | +| `@comptime` | 函数 | 用于编译时执行的辅助函数。 | +| `@derive(...)` | 结构体 | 自动实现 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@` | 任意 | 将泛型属性传递给 C (例如 `@flatten`, `@alias("name")`)。 | + +#### 自定义属性 + +Zen C 支持强大的 **自定义属性** 系统,允许你在代码中直接使用任何 GCC/Clang 的 `__attribute__`。任何不被 Zen C 编译器显式识别的属性都会被视为泛型属性并传递给生成的 C 代码。 + +这提供了对高级编译器特性、优化和链接器指令的访问,而无需在语言核心中提供显式支持。 + +#### 语法映射 +Zen C 属性直接映射到 C 属性: +- `@name` → `__attribute__((name))` +- `@name(args)` → `__attribute__((name(args)))` +- `@name("string")` → `__attribute__((name("string")))` + +#### 智能派生 + +Zen C 提供了尊重移动语义的 "智能派生": + +- **`@derive(Eq)`**:生成一个通过引用获取参数的相等性方法 (`fn eq(self, other: T*)`)。 + - 当比较两个非 Copy 结构体 (`a == b`) 时,编译器会自动通过引用传递 `b` (`&b`) 以避免移动它。 + - 字段上的递归相等性检查也会优先使用指针访问,以防止所有权转移。 + +### 14. 内联汇编 + +Zen C 为内联汇编提供了一流支持,直接转译为 GCC 风格的扩展 `asm`。 + +#### 基本用法 +在 `asm` 块内编写原始汇编。字符串会自动拼接。 +```zc +asm { + "nop" + "mfence" +} +``` + +#### Volatile +防止编译器优化掉具有副作用的汇编代码。 +```zc +asm volatile { + "rdtsc" +} +``` + +#### 命名约束 +Zen C 通过命名绑定简化了复杂的 GCC 约束语法。 + +```zc +// 语法: : out(变量) : in(变量) : clobber(寄存器) +// 使用 {变量} 占位符语法以提高可读性 + +fn add(a: int, b: int) -> int { + let result: int; + asm { + "add {result}, {a}, {b}" + : out(result) + : in(a), in(b) + : clobber("cc") + } + return result; +} +``` + +| 类型 | 语法 | GCC 等效项 | +|:---|:---|:---| +| **输出** | `: out(variable)` | `"=r"(variable)` | +| **输入** | `: in(variable)` | `"r"(variable)` | +| **破坏** | `: clobber("rax")` | `"rax"` | +| **内存** | `: clobber("memory")` | `"memory"` | + +> **注意:** 使用 Intel 语法时(通过 `-masm=intel`),必须确保你的构建配置正确(例如,`//> cflags: -masm=intel`)。TCC 不支持 Intel 语法的汇编。 + +### 15. 构建指令 + +Zen C 支持在源文件顶部使用特殊注释来配置构建过程,无需复杂的构建系统或 Makefile。 + +| 指令 | 参数 | 描述 | +|:---|:---|:---| +| `//> link:` | `-lfoo` 或 `path/to/lib.a` | 链接库或对象文件。 | +| `//> lib:` | `path/to/libs` | 添加库搜索路径 (`-L`)。 | +| `//> include:` | `path/to/headers` | 添加包含头文件搜索路径 (`-I`)。 | +| `//> framework:` | `Cocoa` | 链接 macOS Framework。 | +| `//> cflags:` | `-Wall -O3` | 向 C 编译器传递任意标志。 | +| `//> define:` | `MACRO` 或 `KEY=VAL` | 定义预处理器宏 (`-D`)。 | +| `//> pkg-config:` | `gtk+-3.0` | 运行 `pkg-config` 并追加 `--cflags` 和 `--libs`。 | +| `//> shell:` | `command` | 在构建期间执行 shell 命令。 | +| `//> get:` | `http://url/file` | 如果特定文件不存在,则下载该文件。 | + +#### 特性 + +**1. 操作系统守护 (OS Guarding)** +在指令前加上操作系统名称,以使其仅在特定平台上应用。 +受支持的前缀:`linux:`, `windows:`, `macos:` (或 `darwin:`)。 + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. 环境变量展开** +使用 `${VAR}` 语法在指令中展开环境变量。 + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + +#### 示例 + +```zc +//> include: ./include +//> lib: ./libs +//> link: -lraylib -lm +//> cflags: -Ofast +//> pkg-config: gtk+-3.0 + +import "raylib.h" + +fn main() { ... } +``` + +### 16. 关键字 + +以下关键字在 Zen C 中是保留的。 + +#### 声明 +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### 控制流 +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### 特殊 +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### 常量 +`true`, `false`, `null` + +#### C 保留字 +以下标识符是保留的,因为它们是 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` + +#### 运算符 +`and`, `or` + +--- + +## 标准库 + +Zen C 包含一个涵盖基本功能的标准库 (`std`)。 + +[浏览标准库文档](docs/std/README.md) + +### 核心模块 + +| 模块 | 描述 | 文档 | +| :--- | :--- | :--- | +| **`std/vec.zc`** | 可增长动态数组 `Vec`。 | [文档](docs/std/vec.md) | +| **`std/string.zc`** | 堆分配的 `String` 类型,支持 UTF-8。 | [文档](docs/std/string.md) | +| **`std/queue.zc`** | 先进先出队列 (环形缓冲区)。 | [文档](docs/std/queue.md) | +| **`std/map.zc`** | 泛型哈希表 `Map`。 | [文档](docs/std/map.md) | +| **`std/fs.zc`** | 文件系统操作。 | [文档](docs/std/fs.md) | +| **`std/io.zc`** | 标准输入/输出 (`print`/`println`)。 | [文档](docs/std/io.md) | +| **`std/option.zc`** | 可选值 (`Some`/`None`)。 | [文档](docs/std/option.md) | +| **`std/result.zc`** | 错误处理 (`Ok`/`Err`)。 | [文档](docs/std/result.md) | +| **`std/path.zc`** | 跨平台路径操作。 | [文档](docs/std/path.md) | +| **`std/env.zc`** | 进程环境变量。 | [文档](docs/std/env.md) | +| **`std/net.zc`** | TCP 网络 (套接字)。 | [文档](docs/std/net.md) | +| **`std/thread.zc`** | 线程与同步。 | [文档](docs/std/thread.md) | +| **`std/time.zc`** | 时间测量与睡眠。 | [文档](docs/std/time.md) | +| **`std/json.zc`** | JSON 解析与序列化。 | [文档](docs/std/json.md) | +| **`std/stack.zc`** | 后进先出栈 `Stack`。 | [文档](docs/std/stack.md) | +| **`std/set.zc`** | 泛型哈希集合 `Set`。 | [文档](docs/std/set.md) | + +--- + +## 工具链 + +Zen C 提供内置的语言服务器 (LSP) 和 REPL 以增强开发体验。 + +### 语言服务器 (LSP) + +Zen C 语言服务器 (LSP) 支持标准的 LSP 特性,用于编辑器集成: + +* **转到定义** +* **查找引用** +* **悬停信息** +* **补全** (函数/结构体名,方法/字段的点补全) +* **文档符号** (大纲) +* **签名帮助** +* **诊断** (语法/语义错误) + +启动语言服务器(通常在编辑器的 LSP 设置中配置): + +```bash +zc lsp +``` + +它通过标准 I/O (JSON-RPC 2.0) 进行通信。 + +### REPL + +Read-Eval-Print Loop 允许你交互式地尝试 Zen C 代码。 + +```bash +zc repl +``` + +#### 特性 + +* **交互式编码**:输入表达式或语句以立即求值。 +* **持久历史**:命令保存在 `~/.zprep_history` 中。 +* **启动脚本**:自动加载 `~/.zprep_init.zc` 中的命令。 + +#### 命令 + +| 命令 | 描述 | +|:---|:---| +| `:help` | 显示可用命令。 | +| `:reset` | 清除当前会话历史 (变量/函数)。 | +| `:vars` | 显示活跃变量。 | +| `:funcs` | 显示用户定义的函数。 | +| `:structs` | 显示用户定义的结构体。 | +| `:imports` | 显示活跃导入。 | +| `:history` | 显示会话输入历史。 | +| `:type ` | 显示表达式的类型。 | +| `:c ` | 显示语句生成的 C 代码。 | +| `:time ` | 基准测试表达式 (运行 1000 次迭代)。 | +| `:edit [n]` | 在 `$EDITOR` 中编辑命令 `n` (默认:最后一条)。 | +| `:save ` | 将当前会话保存到 `.zc` 文件。 | +| `:load ` | 将 `.zc` 文件加载并执行到会话中。 | +| `:watch ` | 监视表达式 (每次输入后重新求值)。 | +| `:unwatch ` | 移除监视。 | +| `:undo` | 从会话中移除最后一条命令。 | +| `:delete ` | 移除索引为 `n` 的命令。 | +| `:clear` | 清屏。 | +| `:quit` | 退出 REPL。 | +| `! ` | 运行 shell 命令 (如 `!ls`)。 | + +--- + + +## 编译器支持与兼容性 + +Zen C 旨在与大多数 C11 编译器配合使用。某些特性依赖于 GNU C 扩展,但这些扩展通常在其他编译器中也能工作。使用 `--cc` 标志切换后端。 + +```bash +zc run app.zc --cc clang +zc run app.zc --cc zig +``` + +### 测试套件状态 + +| 编译器 | 通过率 | 受支持特性 | 已知局限性 | +|:---|:---:|:---|:---| +| **GCC** | **100%** | 所有特性 | 无。 | +| **Clang** | **100%** | 所有特性 | 无。 | +| **Zig** | **100%** | 所有特性 | 无。使用 `zig cc` 作为替代 C 编译器。 | +| **TCC** | **~70%** | 基本语法, 泛型, Trait | 不支持 `__auto_type`, 不支持 Intel ASM, 不支持嵌套函数。 | + +> **建议:** 生产环境构建请使用 **GCC**, **Clang**, 或 **Zig**。TCC 非常适合快速原型开发,因为它编译速度极快,但缺少 Zen C 全面支持所需的一些高级 C 扩展。 + +### 使用 Zig 构建 + +Zig 的 `zig cc` 命令提供了 GCC/Clang 的替代方案,具有出色的跨平台编译支持。使用 Zig: + +```bash +# 使用 Zig 编译并运行 Zen C 程序 +zc run app.zc --cc zig + +# 使用 Zig 构建 Zen C 编译器本身 +make zig +``` + +### C++ 互操作 + +Zen C 可以通过 `--cpp` 标志生成 C++ 兼容的代码,从而实现与 C++ 库的无缝集成。 + +```bash +# 直接使用 g++ 编译 +zc app.zc --cpp + +# 或者转译用于手动构建 +zc transpile app.zc --cpp +g++ out.c my_cpp_lib.o -o app +``` + +#### 在 Zen C 中使用 C++ + +包含 C++ 头文件并在 `raw` 块中使用 C++ 代码: + +```zc +include +include + +raw { + std::vector make_vec(int a, int b) { + return {a, b}; + } +} + +fn main() { + let v = make_vec(1, 2); + raw { std::cout << "Size: " << v.size() << std::endl; } +} +``` + +> **注意:** `--cpp` 标志会将后端切换为 `g++` 并发出 C++ 兼容的代码(使用 `auto` 代替 `__auto_type`,使用函数重载代替 `_Generic`,以及对 `void*` 进行显式转换)。 + +#### CUDA 互操作 + +Zen C 通过转译为 **CUDA C++** 来支持 GPU 编程。这使你在维持 Zen C 人体工程学语法的同时,能够利用内核中的强大 C++ 特性(模板、constexpr)。 + +```bash +# 直接使用 nvcc 编译 +zc run app.zc --cuda + +# 或者转译用于手动构建 +zc transpile app.zc --cuda -o app.cu +nvcc app.cu -o app +``` + +#### CUDA 特定属性 + +| 属性 | CUDA 等效项 | 描述 | +|:---|:---|:---| +| `@global` | `__global__` | 内核函数 (运行在 GPU,从主机调用) | +| `@device` | `__device__` | 设备函数 (运行在 GPU,从 GPU 调用) | +| `@host` | `__host__` | 主机函数 (明确仅 CPU 运行) | + +#### 内核启动语法 + +Zen C 提供了一个简洁的 `launch` 语句用于调用 CUDA 内核: + +```zc +launch kernel_name(args) with { + grid: num_blocks, + block: threads_per_block, + shared_mem: 1024, // 可选 + stream: my_stream // 可选 +}; +``` + +这转译为:`kernel_name<<>>(args);` + +#### 编写 CUDA 内核 + +使用带有 `@global` 的 Zen C 函数语法和 `launch` 语句: + +```zc +import "std/cuda.zc" + +@global +fn add_kernel(a: float*, b: float*, c: float*, n: int) { + let i = thread_id(); + if i < n { + c[i] = a[i] + b[i]; + } +} + +fn main() { + def N = 1024; + let d_a = cuda_alloc(N); + let d_b = cuda_alloc(N); + let d_c = cuda_alloc(N); + defer cuda_free(d_a); + defer cuda_free(d_b); + defer cuda_free(d_c); + + // ... 初始化数据 ... + + launch add_kernel(d_a, d_b, d_c, N) with { + grid: (N + 255) / 256, + block: 256 + }; + + cuda_sync(); +} +``` + +#### 标准库 (`std/cuda.zc`) +Zen C 为常见的 CUDA 操作提供了一个标准库,以减少 `raw` 块的使用: + +```zc +import "std/cuda.zc" + +// 内存管理 +let d_ptr = cuda_alloc(1024); +cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float)); +defer cuda_free(d_ptr); + +// 同步 +cuda_sync(); + +// 线程索引 (在内核内部使用) +let i = thread_id(); // 全局索引 +let bid = block_id(); +let tid = local_id(); +``` + + +> **注意:** `--cuda` 标志设置 `nvcc` 为编译器并隐含 `--cpp` 模式。需要安装 NVIDIA CUDA Toolkit。 + +### Objective-C 互操作 + +Zen C 可以通过 `--objc` 标志编译为 Objective-C (`.m`),允许你使用 Objective-C 框架(如 Cocoa/Foundation)和语法。 + +```bash +# 使用 clang (或 gcc/gnustep) 编译 +zc app.zc --objc --cc clang +``` + +#### 在 Zen C 中使用 Objective-C + +使用 `include` 包含头文件,并在 `raw` 块中使用 Objective-C 语法 (`@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(@"来自 Objective-C 的问候!"); + [pool drain]; + } + println "Zen C 也能正常工作!"; +} +``` + +> **注意:** Zen C 字符串插值通过调用 `debugDescription` 或 `description` 同样适用于 Objective-C 对象 (`id`)。 + +--- + +## 贡献 + +我们欢迎各类贡献!无论是修复 Bug、完善文档,还是提出新功能建议。 + +### 如何贡献 +1. **Fork 仓库**:标准的 GitHub 工作流程。 +2. **创建功能分支**:`git checkout -b feature/NewThing`。 +3. **代码规范**: + * 遵循现有的 C 风格。 + * 确保所有测试通过:`make test`。 + * 在 `tests/` 中为你的功能添加新测试。 +4. **提交拉取请求**:清晰地描述你的更改。 + +### 运行测试 +测试套件是你最好的朋友。 + +```bash +# 运行所有测试 (GCC) +make test + +# 运行特定的测试 +./zc run tests/test_match.zc + +# 使用不同的编译器运行 +./tests/run_tests.sh --cc clang +./tests/run_tests.sh --cc zig +./tests/run_tests.sh --cc tcc +``` + +### 扩展编译器 +* **解析器 (Parser)**:`src/parser/` - 递归下降解析器。 +* **代码生成 (Codegen)**:`src/codegen/` - 转译逻辑 (Zen C -> GNU C/C11)。 +* **标准库 (Standard Library)**:`std/` - 使用 Zen C 本身编写。 + +--- + +## 致谢与归属 + +本项目使用了第三方库。完整许可证文本可在 `LICENSES/` 目录中找到。 + +* **[cJSON](https://github.com/DaveGamble/cJSON)** (MIT 许可证):用于语言服务器中的 JSON 解析和生成。 +* **[zc-ape](https://github.com/OEvgeny/zc-ape)** (MIT 许可证):由 [Eugene Olonov](https://github.com/OEvgeny) 开发的原版 Zen-C 实际上便携的可执行文件 (APE) 端口。 +* **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (ISC 许可证):使 APE 成为可能的基础库。 diff --git a/README_ZH_TW.md b/README_ZH_TW.md new file mode 100644 index 0000000..b9b5511 --- /dev/null +++ b/README_ZH_TW.md @@ -0,0 +1,1415 @@ + +
+ +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +
+ +
+ +# Zen C + +**現代開發體驗。零開銷。純淨 C。** + +[![構建狀態](https://img.shields.io/badge/build-passing-brightgreen)]() +[![許可證](https://img.shields.io/badge/license-MIT-blue)]() +[![版本](https://img.shields.io/badge/version-0.1.0-orange)]() +[![平台](https://img.shields.io/badge/platform-linux-lightgrey)]() + +*像高級語言一樣編寫,像 C 一樣運行。* + +
+ +--- + +## 概述 + +**Zen C** 是一種現代系統編程語言,可編譯為人類可讀的 `GNU C`/`C11`。它提供了一套豐富的特性,包括類型推斷、模式匹配、泛型、Trait、async/await 以及具有 RAII 能力的手動內存管理,同時保持 100% 的 C ABI 兼容性。 + +## 社區 + +加入官方 Zen C Discord 服務器,參與討論、展示 Demo、提問或報告 Bug! + +- Discord: [點擊加入](https://discord.com/invite/q6wEsCmkJP) + +--- + +## 目錄 + +- [概述](#概述) +- [社區](#社區) +- [快速入門](#快速入門) + - [安裝](#安裝) + - [用法](#用法) + - [環境變量](#環境變量) +- [語言參考](#語言參考) + - [1. 變量與常量](#1-變量與常量) + - [2. 原始類型](#2-原始類型) + - [3. 複合類型](#3-複合類型) + - [數組](#數組) + - [元組](#元組) + - [結構體](#結構體) + - [不透明結構體](#不透明結構體) + - [枚舉](#枚舉) + - [聯合體](#聯合體) + - [類型別名](#類型別名) + - [不透明類型別名](#不透明類型別名) + - [4. 函數與 Lambda](#4-函數與-lambda) + - [函數](#函數) + - [常量參數](#常量參數) + - [默認參數](#默認參數) + - [Lambda (閉包)](#lambda-閉包) + - [原始函數指針](#原始函數指針) + - [變參函數](#變參函數) + - [5. 控制流](#5-控制流) + - [條件語句](#條件語句) + - [模式匹配](#模式匹配) + - [循環](#循環) + - [高級控制](#高級控制) + - [6. 運算符](#6-運算符) + - [可重載運算符](#可重載運算符) + - [語法糖](#語法糖) + - [7. 打印與字符串插值](#7-打印與字符串插值) + - [關鍵字](#關鍵字) + - [簡寫形式](#簡寫形式) + - [字符串插值 (F-strings)](#字符串插值-f-strings) + - [輸入提示 (`?`)](#輸入提示-) + - [8. 內存管理](#8-內存管理) + - [Defer](#defer) + - [Autofree](#autofree) + - [資源語義 (默認移動)](#資源語義-默認移動) + - [RAII / Drop Trait](#raii--drop-trait) + - [9. 面向對象編程](#9-面向對象編程) + - [方法](#方法) + - [Trait](#trait) + - [標準 Trait](#標準-trait) + - [組合](#組合) + - [10. 泛型](#10-泛型) + - [11. 並發 (Async/Await)](#11-並發-asyncawait) + - [12. 元編程](#12-元編程) + - [Comptime](#comptime) + - [Embed](#embed) + - [插件](#插件) + - [泛型 C 宏](#泛型-c-宏) + - [13. 屬性](#13-屬性) + - [自定義屬性](#自定義屬性) + - [智能派生](#智能派生) + - [14. 內聯匯編](#14-內聯匯編) + - [基本用法](#基本用法) + - [Volatile](#volatile) + - [命名約束](#命名約束) + - [15. 構建指令](#15-構建指令) + - [16. 關鍵字](#16-關鍵字) +- [標準庫](#標準庫) +- [工具鏈](#工具鏈) + - [語言服務器 (LSP)](#語言服務器-lsp) + - [REPL](#repl) +- [編譯器支持與兼容性](#編譯器支持與兼容性) + - [測試套件狀態](#測試套件狀態) + - [使用 Zig 構建](#使用-zig-構建) + - [C++ 互操作](#c-互操作) + - [CUDA 互操作](#cuda-互操作) + - [Objective-C 互操作](#objective-c-互操作) +- [貢獻](#貢獻) +- [致謝與歸屬](#致謝與歸屬) + +--- + +## 快速入門 + +### 安裝 + +```bash +git clone https://github.com/z-libs/Zen-C.git +cd Zen-C +make +sudo make install +``` + +### 便攜式構建 (APE) + +Zen C 可以通過 [Cosmopolitan Libc](https://github.com/jart/cosmopolitan) 編譯為 **Actually Portable Executable (APE)**。這將生成一個單個的可執行文件 (`.com`),能夠原生運行在 Linux, macOS, Windows, FreeBSD, OpenBSD, 和 NetBSD 上的 x86_64 和 aarch64 架構上。 + +**前提條件:** +- `cosmocc` 工具鏈(必須在 PATH 中) + +**構建與安裝:** +```bash +make ape +sudo env "PATH=$PATH" make install-ape +``` + +**產物:** +- `out/bin/zc.com`: 便攜式 Zen-C 編譯器。已將標準庫嵌入到可執行文件中。 +- `out/bin/zc-boot.com`: 一個自包含的引導安裝程序,用於設置新的 Zen-C 項目。 + +**用法:** +```bash +# 在任何支持的操作系統上運行 +./out/bin/zc.com build hello.zc -o hello +``` + +### 用法 + +```bash +# 編譯並運行 +zc run hello.zc + +# 構建可執行文件 +zc build hello.zc -o hello + +# 交互式 Shell +zc repl +``` + +### 環境變量 + +你可以設置 `ZC_ROOT` 來指定標準庫的位置(標準導入如 `import "std/vector.zc"`)。這允許你從任何目錄運行 `zc`。 + +```bash +export ZC_ROOT=/path/to/Zen-C +``` + +--- + +## 語言參考 + +### 1. 變量與常量 + +Zen C 區分編譯時常量和運行時變量。 + +#### 清單常量 (`def`) +僅在編譯時存在的值(折疊到代碼中)。用於數組大小、固定配置和魔術數字。 + +```zc +def MAX_SIZE = 1024; +let buffer: char[MAX_SIZE]; // 有效的數組大小 +``` + +#### 變量 (`let`) +內存中的存儲位置。可以是可變的或只讀的 (`const`)。 + +```zc +let x = 10; // 可變 +x = 20; // 允許 + +let y: const int = 10; // 只讀 (類型修飾) +// y = 20; // 錯誤:無法賦值給 const 變量 +``` + +### 2. 原始類型 + +| 類型 | C 等效類型 | 描述 | +|:---|:---|:---| +| `int`, `uint` | `int`, `unsigned int` | 平台標準整數 | +| `I8` .. `I128` 或 `i8` .. `i128` | `int8_t` .. `__int128_t` | 有符號固定寬度整數 | +| `U8` .. `U128` 或 `u8` .. `u128` | `uint8_t` .. `__uint128_t` | 無符號固定寬度整數 | +| `isize`, `usize` | `ptrdiff_t`, `size_t` | 指針大小的整數 | +| `byte` | `uint8_t` | U8 的別名 | +| `F32`, `F64` 或 `f32`, `f64` | `float`, `double` | 浮點數 | +| `bool` | `bool` | `true` 或 `false` | +| `char` | `char` | 單個字符 | +| `string` | `char*` | C-string (以 null 結尾) | +| `U0`, `u0`, `void` | `void` | 空類型 | + +### 3. 複合類型 + +#### 數組 +具有值語義的固定大小數組。 +```zc +def SIZE = 5; +let ints: int[SIZE] = [1, 2, 3, 4, 5]; +let zeros: [int; SIZE]; // 零初始化的 +``` + +#### 元組 +將多個值組合在一起,通過索引訪問元素。 +```zc +let pair = (1, "Hello"); +let x = pair.0; // 1 +let s = pair.1; // "Hello" +``` + +**多個返回值** + +函數可以返回元組以提供多個結果: +```zc +fn add_and_subtract(a: int, b: int) -> (int, int) { + return (a + b, a - b); +} + +let result = add_and_subtract(3, 2); +let sum = result.0; // 5 +let diff = result.1; // 1 +``` + +**解構** + +元組可以直接解構為多個變量: +```zc +let (sum, diff) = add_and_subtract(3, 2); +// sum = 5, diff = 1 +``` + +#### 結構體 +帶有可選位域的數據結構。 +```zc +struct Point { + x: int; + y: int; +} + +// 結構體初始化 +let p = Point { x: 10, y: 20 }; + +// 位域 +struct Flags { + valid: U8 : 1; + mode: U8 : 3; +} +``` + +> **注意**:結構體默認使用 [移動語義](#資源語義-默認移動)。即使是指針,也可以通過 `.` 訪問字段(自動解引用)。 + +#### 不透明結構體 +你可以將結構體定義為 `opaque`,以將對其字段的訪問限制在定義該結構體的模塊內部,同時仍允許在棧上分配該結構體(大小已知)。 + +```zc +// 在 user.zc 中 +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // 允許:在模塊內部 +} + +// 在 main.zc 中 +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // 錯誤:無法訪問私有字段 'id' +} +``` + +#### 枚舉 +能夠持有數據的標籤聯合 (Sum types)。 +```zc +enum Shape { + Circle(float), // 持有半徑 + Rect(float, float), // 持有寬、高 + Point // 不帶數據 +} +``` + +#### 聯合體 +標準 C 聯合體(不安全訪問)。 +```zc +union Data { + i: int; + f: float; +} +``` + +#### 類型別名 +為現有類型創建新名稱。 +```zc +alias ID = int; +alias PointMap = Map; +``` + +#### 不透明類型別名 +你可以將類型別名定義為 `opaque`,從而在定義模塊之外創建一個與基礎類型不同的新類型。這提供了強大的封裝和類型安全性,而沒有包裝結構體的運行時開銷。 + +```zc +// 在 library.zc 中 +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // 允許在模塊內部進行隱式轉換 +} + +// 在 main.zc 中 +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // 錯誤:類型驗證失敗 + // let h2: Handle = 10; // 錯誤:類型驗證失敗 +} +``` + +### 4. 函數與 Lambda + +#### 函數 +```zc +fn add(a: int, b: int) -> int { + return a + b; +} + +// 調用時支持命名參數 +add(a: 10, b: 20); +``` + +> **注意**:命名參數必須嚴格遵循定義的參數順序。`add(b: 20, a: 10)` 是無效的。 + +#### 常量參數 +函數參數可以標記為 `const` 以強制執行只讀語義。這是一個類型修飾符,而不是清單常量。 + +```zc +fn print_val(v: const int) { + // v = 10; // 錯誤:無法賦值給 const 變量 + println "{v}"; +} +``` + +#### 默認參數 +函數可以為尾部參數定義默認值。這些值可以是字面量、表達式或有效的 Zen C 代碼(如結構體構造函數)。 +```zc +// 簡單默認值 +fn increment(val: int, amount: int = 1) -> int { + return val + amount; +} + +// 表達式默認值(在調用處計算) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// 結構體默認值 +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "調試模式"; } +} + +fn main() { + increment(10); // 11 + offset(5); // 25 + init(); // 打印 "調試模式" +} +``` + +#### Lambda (閉包) +可以捕獲環境的匿名函數。 +```zc +let factor = 2; +let double = x -> x * factor; // 箭頭語法 +let full = fn(x: int) -> int { return x * factor; }; // 塊語法 +``` + +#### 原始函數指針 +Zen C 使用 `fn*` 語法支持原始 C 函數指針。這允許與期望函數指針且沒有閉包開銷的 C 庫進行無縫互操作。 + +```zc +// 接受原始函數指針的函數 +fn set_callback(cb: fn*(int)) { + cb(42); +} + +// 返回原始函數指針的函數 +fn get_callback() -> fn*(int) { + return my_handler; +} + +// 支持指向函數指針的指針 (fn**) +let pptr: fn**(int) = &ptr; +``` + +#### 變參函數 +函數可以使用 `...` 和 `va_list` 類型接受可變數量的參數。 +```zc +fn log(lvl: int, fmt: char*, ...) { + let ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // 使用 C stdio + va_end(ap); +} +``` + +### 5. 控制流 + +#### 条件语句 +```zc +if x > 10 { + print("Large"); +} else if x > 5 { + print("Medium"); +} else { + print("Small"); +} + +// 三元運算符 +let y = x > 10 ? 1 : 0; +``` + +#### 模式匹配 +`switch` 的強大替代方案。 +```zc +match val { + 1 => { print "One" }, + 2 || 3 => { print "Two or Three" }, // 使用 || 進行 或 操作 + 4 or 5 => { print "Four or Five" }, // 使用 'or' 進行 或 操作 + 6, 7, 8 => { print "Six to Eight" }, // 使用逗號進行 或 操作 + 10 .. 15 => { print "10 to 14" }, // 左閉右開區間 (舊語法) + 10 ..< 15 => { print "10 to 14" }, // 左閉右開區間 (顯式) + 20 ..= 25 => { print "20 to 25" }, // 全閉區間 + _ => { print "Other" }, +} + +// 解構枚舉 +match shape { + Shape::Circle(r) => println "半徑: {r}", + Shape::Rect(w, h) => println "面積: {w*h}", + Shape::Point => println "點" +} +``` + +#### 引用綁定 +為了在不獲取所有權(移動)的情況下檢查一個值,在模式中使用 `ref` 關鍵字。這對於實現了移動語義的類型(如 `Option`, `Result`, 非 Copy 結構體)至關重要。 + +```zc +let opt = Some(NonCopyVal{...}); +match opt { + Some(ref x) => { + // 'x' 是指向 'opt' 內部值的指針 + // 'opt' 在此處不會被移動/消耗 + println "{x.field}"; + }, + None => {} +} +``` + +#### 循環 +```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 step 2 { ... } + +// 迭代器 (Vec, Array, 或自定義 Iterable) +for item in collection { ... } + +// While 循環 +while x < 10 { ... } + +// 帶標籤的無限循環 +outer: loop { + if done { break outer; } +} + +// 重複 N 次 +for _ in 0..5 { ... } +``` + +#### 高級控制 +```zc +// Guard: 如果條件為假,則執行 else 塊並返回 +guard ptr != NULL else { return; } + +// Unless: 除非為真(即如果為假) +unless is_valid { return; } +``` + +### 6. 運算符 + +Zen C 通過實現特定的方法名來支持用戶定義結構體的運算符重載。 + +#### 可重載運算符 + +| 類別 | 運算符 | 方法名 | +|:---|:---|:---| +| **算術** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **比較** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **位運算** | `&`, `|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **一元** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **索引** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +> **關於字符串相等性的說明**: +> - `string == string` 進行 **值比較**(等同於 `strcmp`)。 +> - `char* == char*` 進行 **指針比較**(檢查內存地址)。 +> - 混合比較(例如 `string == char*`)默認為 **指針比較**。 + +**示例:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +let p3 = p1 + p2; // 調用 p1.add(p2) +``` + +#### 語法糖 + +這些運算符是內置語言特性,不能直接重載。 + +| 運算符 | 名稱 | 描述 | +|:---|:---|:---| +| `|>` | 管道 | `x |> f(y)` 脫糖為 `f(x, y)` | +| `??` | 空合併 | 如果 `val` 為 NULL,`val ?? default` 返回 `default` (用於指針) | +| `??=` | 空賦值 | 如果 `val` 為 NULL 則賦值 | +| `?.` | 安全導航 | 僅當 `ptr` 不為 NULL 時訪問字段 | +| `?` | Try 運算符 | 如果存在錯誤則返回 (用於 Result/Option 類型) | + +**自動解引用**: +指針字段訪問 (`ptr.field`) 和方法調用 (`ptr.method()`) 會自動解引用指針,等同於 `(*ptr).field`。 + +### 7. 打印與字符串插值 + +Zen C 提供了多種控制台打印選項,包括關鍵字和簡潔的簡寫形式。 + +#### 關鍵字 + +- `print "text"`: 打印到 `stdout`,不帶尾隨換行符。 +- `println "text"`: 打印到 `stdout`,帶尾隨換行符。 +- `eprint "text"`: 打印到 `stderr`,不帶尾隨換行符。 +- `eprintln "text"`: 打印到 `stderr`,帶尾隨換行符。 + +#### 簡寫形式 + +Zen C 允許直接將字符串字面量用作語句來進行快速打印: + +- `"Hello World"`: 等同於 `println "Hello World"`。(隱式添加換行符) +- `"Hello World"..`: 等同於 `print "Hello World"`。(不帶尾隨換行符) +- `!"Error"`: 等同於 `eprintln "Error"`。(輸出到 stderr) +- `!"Error"..`: 等同於 `eprint "Error"`。(輸出到 stderr,不帶換行符) + +#### 字符串插值 (F-strings) + +你可以使用 `{}` 語法將表達式直接嵌入到字符串字面量中。這適用於所有打印方法和字符串簡寫。 + +```zc +let x = 42; +let name = "Zen"; +println "值: {x}, 名稱: {name}"; +"值: {x}, 名稱: {name}"; // 簡寫形式的 println +``` + +#### 輸入提示 (`?`) + +Zen C 支持使用 `?` 前綴進行用戶輸入提示的簡寫。 + +- `? "提示文本"`: 打印提示信息(不換行)並等待輸入(讀取一行)。 +- `? "輸入年齡: " (age)`: 打印提示並掃描輸入到變量 `age` 中。 + - 格式說明符會根據變量類型自動推斷。 + +```c +let age: int; +? "你多大了? " (age); +println "你 {age} 歲了。"; +``` + +### 8. 內存管理 + +Zen C 允許帶有符合人體工程學輔助的手動內存管理。 + +#### Defer +在當前作用域退出時執行代碼。Defer 語句按照後進先出 (LIFO) 的順序執行。 +```zc +let f = fopen("file.txt", "r"); +defer fclose(f); +``` + +> 為了防止未定義行為,`defer` 塊內不允許使用控制流語句(`return`, `break`, `continue`, `goto`)。 + +#### Autofree +在作用域退出時自動釋放變量。 +```zc +autofree let types = malloc(1024); +``` + +#### 資源語義 (默認移動) +Zen C 將帶有析構函數(如 `File`, `Vec`, 或 malloc 的指針)的類型視為 **資源**。為了防止雙重釋放錯誤,資源不能被隱式複製。 + +- **默認移動**:分配資源變量會轉移所有權。原始變量變得無效(已移動)。 +- **複製類型**:沒有析構函數的類型可以申請參與 `Copy` 行為,使賦值變成複製。 + +**診斷與哲學**: +如果你看到錯誤 "Use of moved value",編譯器是在告訴你:*"此類型擁有一個資源(如內存或句柄),盲目複製它是不安全的。"* + +> **對比:** 與 C/C++ 不同,Zen C 不會隱式複製擁有資源的值。 + +**函數參數**: +將值傳遞給函數遵循與賦值相同的規則:資源會被移動,除非通過引用傳遞。 + +```zc +fn process(r: Resource) { ... } // 'r' 被移動進函數 +fn peek(r: Resource*) { ... } // 'r' 被借用 (引用) +``` + +**顯式克隆**: +如果你 *確實* 想要一個資源的兩個副本,請顯式執行: + +```zc +let b = a.clone(); // 調用 Clone trait 中的 'clone' 方法 +``` + +**選擇性複製 (值類型)**: +對於沒有析構函數的小型類型: + +```zc +struct Point { x: int; y: int; } +impl Copy for Point {} // 選擇參與隱式複製 + +fn main() { + let p1 = Point { x: 1, y: 2 }; + let p2 = p1; // 已複製。p1 保持有效。 +} +``` + +#### RAII / Drop Trait +實現 `Drop` 以自動運行清理邏輯。 +```zc +impl Drop for MyStruct { + fn drop(self) { + self.free(); + } +} +``` + +### 9. 面向對象編程 + +#### 方法 +使用 `impl` 為類型定義方法。 +```zc +impl Point { + // 靜態方法 (構造函數慣例) + fn new(x: int, y: int) -> Self { + return Point{x: x, y: y}; + } + + // 實例方法 + fn dist(self) -> float { + return sqrt(self.x * self.x + self.y * self.y); + } +} +``` + +#### Trait +定義共享行為。 +```zc +struct Circle { radius: f32; } + +trait Drawable { + fn draw(self); +} + +impl Drawable for Circle { + fn draw(self) { ... } +} + +let circle = Circle{}; +let drawable: Drawable = &circle; +``` + +#### 標準 Trait +Zen C 包含與語言語法集成的標準 Trait。 + +**Iterable** + +實現 `Iterable` 以便為你的自定義類型啟用 `for-in` 循環。 + +```zc +import "std/iter.zc" + +// 定義一個迭代器 +struct MyIter { + curr: int; + stop: int; +} + +impl MyIter { + fn next(self) -> Option { + if self.curr < self.stop { + self.curr += 1; + return Option::Some(self.curr - 1); + } + return Option::None(); + } +} + +// 實現 Iterable +impl MyRange { + fn iterator(self) -> MyIter { + return MyIter{curr: self.start, stop: self.end}; + } +} + +// 在循環中使用 +for i in my_range { + println "{i}"; +} +``` + +**Drop** + +實現 `Drop` 來定義一個在對象超出範圍時運行的析構函數 (RAII)。 + +```zc +import "std/mem.zc" + +struct Resource { + ptr: void*; +} + +impl Drop for Resource { + fn drop(self) { + if self.ptr != NULL { + free(self.ptr); + } + } +} +``` + +> **注意:** 如果一個變量被移動,則原始變量不會調用 `drop`。它遵循 [資源語義](#資源語義-默認移動)。 + +**Copy** + +標記 Trait,用於選擇支持 `Copy` 行為(隱式複製)而不是移動語義。通過 `@derive(Copy)` 使用。 + +> **規則:** 實現了 `Copy` 的類型不得定義析構函數 (`Drop`)。 + +```zc +@derive(Copy) +struct Point { x: int; y: int; } + +fn main() { + let p1 = Point{x: 1, y: 2}; + let p2 = p1; // 已複製!p1 保持有效。 +} +``` + +**Clone** + +實現 `Clone` 以允許顯式複製擁有資源的類型。 + +```zc +import "std/mem.zc" + +struct MyBox { val: int; } + +impl Clone for MyBox { + fn clone(self) -> MyBox { + return MyBox{val: self.val}; + } +} + +fn main() { + let b1 = MyBox{val: 42}; + let b2 = b1.clone(); // 顯式複製 +} +``` + +#### 組合 +使用 `use` 嵌入其他結構體。你可以將它們混合進來(展平字段)或者為它們命名(嵌套字段)。 + +```zc +struct Entity { id: int; } + +struct Player { + // 混入 (未命名): 展平字段 + use Entity; // 直接將 'id' 添加到 Player + name: string; +} + +struct Match { + // 組合 (命名): 嵌套字段 + use p1: Player; // 通過 match.p1 訪問 + use p2: Player; // 通過 match.p2 訪問 +} +``` + +### 10. 泛型 + +結構體和函數的類型安全模板。 + +```zc +// 泛型結構體 +struct Box { + item: T; +} + +// 泛型函數 +fn identity(val: T) -> T { + return val; +} + +// 多參數泛型 +struct Pair { + key: K; + value: V; +} +``` + +### 11. 並發 (Async/Await) + +基於 pthreads 構建。 + +```zc +async fn fetch_data() -> string { + // 在後台運行 + return "Data"; +} + +fn main() { + let future = fetch_data(); + let result = await future; +} +``` + +### 12. 元編程 + +#### Comptime +在編譯時運行代碼以生成源碼或打印消息。 +```zc +comptime { + // 在編譯時生成代碼 (寫入 stdout) + println "let build_date = \"2024-01-01\";"; +} + +println "構建日期: {build_date}"; +``` + +#### Embed +將文件嵌入為指定類型。 +```zc +// 默認 (Slice_char) +let data = embed "assets/logo.png"; + +// 類型化嵌入 +let text = embed "shader.glsl" as string; // 嵌入為 C-string +let rom = embed "bios.bin" as u8[1024]; // 嵌入為固定數組 +let wav = embed "sound.wav" as u8[]; // 嵌入為 Slice_u8 +``` + +#### 插件 +導入編譯器插件以擴展語法。 +```zc +import plugin "regex" +let re = regex! { ^[a-z]+$ }; +``` + +#### 泛型 C 宏 +將預處理器宏傳遞給 C。 + +> **提示**:對於簡單的常量,請使用 `def`。當你需要 C 預處理器宏或條件編譯標誌時,請使用 `#define`。 + +```zc +#define MAX_BUFFER 1024 +``` + +### 13. 屬性 + +修飾函數和結構體以修改編譯器行為。 + +| 屬性 | 作用域 | 描述 | +|:---|:---|:---| +| `@must_use` | 函數 | 如果忽略返回值則發出警告。 | +| `@deprecated("msg")` | 函數/結構體 | 使用時發出帶有消息的警告。 | +| `@inline` | 函數 | 提示編譯器進行內聯。 | +| `@noinline` | 函數 | 防止內聯。 | +| `@packed` | 結構體 | 移除字段間的填充。 | +| `@align(N)` | 結構體 | 強制按 N 字节對齊。 | +| `@constructor` | 函數 | 在 main 之前運行。 | +| `@destructor` | 函數 | 在 main 退出後運行。 | +| `@unused` | 函數/變量 | 抑制未使用變量警告。 | +| `@weak` | 函數 | 弱符號鏈接。 | +| `@section("name")` | 函數 | 將代碼放置在特定段中。 | +| `@noreturn` | 函數 | 函數不會返回 (例如 exit)。 | +| `@pure` | 函數 | 函數無副作用 (優化提示)。 | +| `@cold` | 函數 | 函數不太可能被執行 (分支預測提示)。 | +| `@hot` | 函數 | 函數頻繁執行 (優化提示)。 | +| `@export` | 函數/結構體 | 導出符號 (默認可見性)。 | +| `@global` | 函數 | CUDA: 內核入口點 (`__global__`)。 | +| `@device` | 函數 | CUDA: 設備函數 (`__device__`)。 | +| `@host` | 函數 | CUDA: 主機函數 (`__host__`)。 | +| `@comptime` | 函數 | 用於編譯時執行的輔助函數。 | +| `@derive(...)` | 結構體 | 自動實現 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@` | 任意 | 將泛型屬性傳遞給 C (例如 `@flatten`, `@alias("name")`)。 | + +#### 自定義屬性 + +Zen C 支持強大的 **自定義屬性** 系統,允許你在代碼中直接使用任何 GCC/Clang 的 `__attribute__`。任何不被 Zen C 編譯器顯式識別的屬性都會被視為泛型屬性並傳遞給生成的 C 代碼。 + +這提供了對高級編譯器特性、優化和鏈接器指令的訪問,而無需在語言核心中提供顯式支持。 + +#### 語法映射 +Zen C 屬性直接映射到 C 屬性: +- `@name` → `__attribute__((name))` +- `@name(args)` → `__attribute__((name(args)))` +- `@name("string")` → `__attribute__((name("string")))` + +#### 智能派生 + +Zen C 提供了尊重移動語義的 "智能派生": + +- **`@derive(Eq)`**:生成一個通過引用獲取參數的相等性方法 (`fn eq(self, other: T*)`)。 + - 當比較兩個非 Copy 結構體 (`a == b`) 時,編譯器會自動通過引用傳遞 `b` (`&b`) 以避免移動它。 + - 字段上的遞歸相等性檢查也會優先使用指針訪問,以防止所有權轉移。 + +### 14. 內聯匯編 + +Zen C 為內聯匯編提供了一流支持,直接轉譯為 GCC 風格的擴展 `asm`。 + +#### 基本用法 +在 `asm` 塊內編寫原始匯編。字符串會自動拼接。 +```zc +asm { + "nop" + "mfence" +} +``` + +#### Volatile +防止編譯器優化掉具有副作用的匯編代碼。 +```zc +asm volatile { + "rdtsc" +} +``` + +#### 命名約束 +Zen C 通過命名綁定簡化了複雜的 GCC 約束語法。 + +```zc +// 語法: : out(變量) : in(變量) : clobber(寄存器) +// 使用 {變量} 佔位符語法以提高可讀性 + +fn add(a: int, b: int) -> int { + let result: int; + asm { + "add {result}, {a}, {b}" + : out(result) + : in(a), in(b) + : clobber("cc") + } + return result; +} +``` + +| 類型 | 語法 | GCC 等效項 | +|:---|:---|:---| +| **輸出** | `: out(variable)` | `"=r"(variable)` | +| **輸入** | `: in(variable)` | `"r"(variable)` | +| **破壞** | `: clobber("rax")` | `"rax"` | +| **內存** | `: clobber("memory")` | `"memory"` | + +> **注意:** 使用 Intel 語法時(通過 `-masm=intel`),必須確保你的構建配置正確(例如,`//> cflags: -masm=intel`)。TCC 不支持 Intel 語法的匯編。 + +### 15. 構建指令 + +Zen C 支持在源文件頂部使用特殊註釋來配置構建過程,無需複雜的構建系統或 Makefile。 + +| 指令 | 參數 | 描述 | +|:---|:---|:---| +| `//> link:` | `-lfoo` 或 `path/to/lib.a` | 鏈接庫或對象文件。 | +| `//> lib:` | `path/to/libs` | 添加庫搜索路徑 (`-L`)。 | +| `//> include:` | `path/to/headers` | 添加包含頭文件搜索路徑 (`-I`)。 | +| `//> framework:` | `Cocoa` | 鏈接 macOS Framework。 | +| `//> cflags:` | `-Wall -O3` | 向 C 編譯器傳遞任意標誌。 | +| `//> define:` | `MACRO` 或 `KEY=VAL` | 定義預處理器宏 (`-D`)。 | +| `//> pkg-config:` | `gtk+-3.0` | 運行 `pkg-config` 並追加 `--cflags` 和 `--libs`。 | +| `//> shell:` | `command` | 在構建期間執行 shell 命令。 | +| `//> get:` | `http://url/file` | 如果特定文件不存在,則下載該文件。 | + +#### 特性 + +**1. 操作系統守護 (OS Guarding)** +在指令前加上操作系統名稱,以使其僅在特定平台上應用。 +受支持的前綴:`linux:`, `windows:`, `macos:` (或 `darwin:`)。 + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. 環境變量展開** +使用 `${VAR}` 語法在指令中展開環境變量。 + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + +#### 示例 + +```zc +//> include: ./include +//> lib: ./libs +//> link: -lraylib -lm +//> cflags: -Ofast +//> pkg-config: gtk+-3.0 + +import "raylib.h" + +fn main() { ... } +``` + +### 16. 關鍵字 + +以下關鍵字在 Zen C 中是保留的。 + +#### 聲明 +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### 控制流 +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### 特殊 +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### 常量 +`true`, `false`, `null` + +#### C 保留字 +以下標識符是保留的,因為它們是 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` + +#### 運算符 +`and`, `or` + +--- + +## 標準庫 + +Zen C 包含一個涵蓋基本功能的標準庫 (`std`)。 + +[瀏覽標準庫文檔](docs/std/README.md) + +### 核心模塊 + +| 模塊 | 描述 | 文檔 | +| :--- | :--- | :--- | +| **`std/vec.zc`** | 可增長動態數組 `Vec`。 | [文檔](docs/std/vec.md) | +| **`std/string.zc`** | 堆分配的 `String` 類型,支持 UTF-8。 | [文檔](docs/std/string.md) | +| **`std/queue.zc`** | 先進先出隊列 (環形緩衝區)。 | [文檔](docs/std/queue.md) | +| **`std/map.zc`** | 泛型哈希表 `Map`。 | [文檔](docs/std/map.md) | +| **`std/fs.zc`** | 文件系統操作。 | [文檔](docs/std/fs.md) | +| **`std/io.zc`** | 標準輸入/輸出 (`print`/`println`)。 | [文檔](docs/std/io.md) | +| **`std/option.zc`** | 可選值 (`Some`/`None`)。 | [文檔](docs/std/option.md) | +| **`std/result.zc`** | 錯誤處理 (`Ok`/`Err`)。 | [文檔](docs/std/result.md) | +| **`std/path.zc`** | 跨平台路徑操作。 | [文檔](docs/std/path.md) | +| **`std/env.zc`** | 進程環境變量。 | [文檔](docs/std/env.md) | +| **`std/net.zc`** | TCP 網絡 (套接字)。 | [文檔](docs/std/net.md) | +| **`std/thread.zc`** | 線程與同步。 | [文檔](docs/std/thread.md) | +| **`std/time.zc`** | 時間測量與睡眠。 | [文檔](docs/std/time.md) | +| **`std/json.zc`** | JSON 解析與序列化。 | [文檔](docs/std/json.md) | +| **`std/stack.zc`** | 後進先出棧 `Stack`。 | [文檔](docs/std/stack.md) | +| **`std/set.zc`** | 泛型哈希集合 `Set`。 | [文檔](docs/std/set.md) | + +--- + +## 工具鏈 + +Zen C 提供內置的語言服務器 (LSP) 和 REPL 以增強開發體驗。 + +### 語言服務器 (LSP) + +Zen C 語言服務器 (LSP) 支持標準的 LSP 特性,用於編輯器集成: + +* **轉到定義** +* **查找引用** +* **懸停信息** +* **補全** (函數/結構體名,方法/字段的點補全) +* **文檔符號** (大綱) +* **簽名幫助** +* **診斷** (語法/語義錯誤) + +啟動語言服務器(通常在編輯器的 LSP 設置中配置): + +```bash +zc lsp +``` + +它通過標準 I/O (JSON-RPC 2.0) 進行通信。 + +### REPL + +Read-Eval-Print Loop 允許你交互式地嘗試 Zen C 代碼。 + +```bash +zc repl +``` + +#### 特性 + +* **交互式編碼**:輸入表達式或語句以立即求值。 +* **持久歷史**:命令保存在 `~/.zprep_history` 中。 +* **啟動腳本**:自動加載 `~/.zprep_init.zc` 中的命令。 + +#### 命令 + +| 命令 | 描述 | +|:---|:---| +| `:help` | 顯示可用命令。 | +| `:reset` | 清除當前會話歷史 (變量/函數)。 | +| `:vars` | 顯示活躍變量。 | +| `:funcs` | 顯示用戶定義的函數。 | +| `:structs` | 顯示用戶定義的結構體。 | +| `:imports` | 顯示活躍導入。 | +| `:history` | 顯示會話輸入歷史。 | +| `:type ` | 顯示表達式的類型。 | +| `:c ` | 顯示語句生成的 C 代碼。 | +| `:time ` | 基准測試表達式 (運行 1000 次迭代)。 | +| `:edit [n]` | 在 `$EDITOR` 中編輯命令 `n` (默認:最後一條)。 | +| `:save ` | 將當前會話保存到 `.zc` 文件。 | +| `:load ` | 將 `.zc` 文件加載並執行到會話中。 | +| `:watch ` | 監視表達式 (每次輸入後重新求值)。 | +| `:unwatch ` | 移除監視。 | +| `:undo` | 從會話中移除最後一條命令。 | +| `:delete ` | 移除索引為 `n` 的命令。 | +| `:clear` | 清屏。 | +| `:quit` | 退出 REPL。 | +| `! ` | 運行 shell 命令 (如 `!ls`)。 | + +--- + + +## 編譯器支持與兼容性 + +Zen C 旨在與大多數 C11 編譯器配合使用。某些特性依賴於 GNU C 擴展,但這些擴展通常在其他編譯器中也能工作。使用 `--cc` 標誌切換後端。 + +```bash +zc run app.zc --cc clang +zc run app.zc --cc zig +``` + +### 測試套件狀態 + +| 編譯器 | 通過率 | 受支持特性 | 已知局限性 | +|:---|:---:|:---|:---| +| **GCC** | **100%** | 所有特性 | 無。 | +| **Clang** | **100%** | 所有特性 | 無。 | +| **Zig** | **100%** | 所有特性 | 無。使用 `zig cc` 作為替代 C 編譯器。 | +| **TCC** | **~70%** | 基本語法, 泛型, Trait | 不支持 `__auto_type`, 不支持 Intel ASM, 不支持嵌套函數。 | + +> **建議:** 生產環境構建請使用 **GCC**, **Clang**, 或 **Zig**。TCC 非常適合快速原型開發,因為它編譯速度極快,但缺少 Zen C 全面支持所需的一些高級 C 擴展。 + +### 使用 Zig 構建 + +Zig 的 `zig cc` 命令提供了 GCC/Clang 的替代方案,具有出色的跨平台編譯支持。使用 Zig: + +```bash +# 使用 Zig 編譯並運行 Zen C 程序 +zc run app.zc --cc zig + +# 使用 Zig 構建 Zen C 編譯器本身 +make zig +``` + +### C++ 互操作 + +Zen C 可以通過 `--cpp` 標誌生成 C++ 兼容的代碼,從而實現與 C++ 庫的無縫集成。 + +```bash +# 直接使用 g++ 編譯 +zc app.zc --cpp + +# 或者轉譯用於手動構建 +zc transpile app.zc --cpp +g++ out.c my_cpp_lib.o -o app +``` + +#### 在 Zen C 中使用 C++ + +包含 C++ 頭文件並在 `raw` 塊中使用 C++ 代碼: + +```zc +include +include + +raw { + std::vector make_vec(int a, int b) { + return {a, b}; + } +} + +fn main() { + let v = make_vec(1, 2); + raw { std::cout << "Size: " << v.size() << std::endl; } +} +``` + +> **注意:** `--cpp` 標誌會將後端切換為 `g++` 並發出 C++ 兼容的代碼(使用 `auto` 代替 `__auto_type`,使用函數重載代替 `_Generic`,以及對 `void*` 進行顯式轉換)。 + +#### CUDA 互操作 + +Zen C 通過轉譯為 **CUDA C++** 來支持 GPU 編程。這使你在維持 Zen C 人體工程學語法的同時,能夠利用內核中的強大 C++ 特性(模板、constexpr)。 + +```bash +# 直接使用 nvcc 編譯 +zc run app.zc --cuda + +# 或者轉譯用於手動構建 +zc transpile app.zc --cuda -o app.cu +nvcc app.cu -o app +``` + +#### CUDA 特定屬性 + +| 屬性 | CUDA 等效項 | 描述 | +|:---|:---|:---| +| `@global` | `__global__` | 內核函數 (運行在 GPU,從主機調用) | +| `@device` | `__device__` | 設備函數 (運行在 GPU,從 GPU 調用) | +| `@host` | `__host__` | 主機函數 (明確僅 CPU 運行) | + +#### 內核啟動語法 + +Zen C 提供了一個簡潔的 `launch` 語句用於調用 CUDA 內核: + +```zc +launch kernel_name(args) with { + grid: num_blocks, + block: threads_per_block, + shared_mem: 1024, // 可選 + stream: my_stream // 可選 +}; +``` + +這轉譯為:`kernel_name<<>>(args);` + +#### 編寫 CUDA 內核 + +使用帶有 `@global` 的 Zen C 函數語法和 `launch` 語句: + +```zc +import "std/cuda.zc" + +@global +fn add_kernel(a: float*, b: float*, c: float*, n: int) { + let i = thread_id(); + if i < n { + c[i] = a[i] + b[i]; + } +} + +fn main() { + def N = 1024; + let d_a = cuda_alloc(N); + let d_b = cuda_alloc(N); + let d_c = cuda_alloc(N); + defer cuda_free(d_a); + defer cuda_free(d_b); + defer cuda_free(d_c); + + // ... 初始化數據 ... + + launch add_kernel(d_a, d_b, d_c, N) with { + grid: (N + 255) / 256, + block: 256 + }; + + cuda_sync(); +} +``` + +#### 標準庫 (`std/cuda.zc`) +Zen C 為常見的 CUDA 操作提供了一個標準庫,以減少 `raw` 塊的使用: + +```zc +import "std/cuda.zc" + +// 內存管理 +let d_ptr = cuda_alloc(1024); +cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float)); +defer cuda_free(d_ptr); + +// 同步 +cuda_sync(); + +// 線程索引 (在內核內部使用) +let i = thread_id(); // 全局索引 +let bid = block_id(); +let tid = local_id(); +``` + + +> **注意:** `--cuda` 標誌設置 `nvcc` 為編譯器並隱含 `--cpp` 模式。需要安裝 NVIDIA CUDA Toolkit。 + +### Objective-C 互操作 + +Zen C 可以通過 `--objc` 標誌編譯為 Objective-C (`.m`),允許你使用 Objective-C 框架(如 Cocoa/Foundation)和語法。 + +```bash +# 使用 clang (或 gcc/gnustep) 編譯 +zc app.zc --objc --cc clang +``` + +#### 在 Zen C 中使用 Objective-C + +使用 `include` 包含頭文件,並在 `raw` 塊中使用 Objective-C 語法 (`@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(@"來自 Objective-C 的問候!"); + [pool drain]; + } + println "Zen C 也能正常工作!"; +} +``` + +> **注意:** Zen C 字符串插值通過調用 `debugDescription` 或 `description` 同樣適用於 Objective-C 對象 (`id`)。 + +--- + +## 貢獻 + +我們歡迎各類貢獻!無論是修復 Bug、完善文檔,還是提出新功能建議。 + +### 如何貢獻 +1. **Fork 倉庫**:標準的 GitHub 工作流程。 +2. **創建功能分支**:`git checkout -b feature/NewThing`。 +3. **代碼規範**: + * 遵循現有的 C 風格。 + * 確保所有測試通過:`make test`。 + * 在 `tests/` 中為你的功能添加新測試。 +4. **提交拉取請求**:清晰地描述你的更改。 + +### 運行測試 +測試套件是你最好的朋友。 + +```bash +# 運行所有測試 (GCC) +make test + +# 運行特定的測試 +./zc run tests/test_match.zc + +# 使用不同的編譯器運行 +./tests/run_tests.sh --cc clang +./tests/run_tests.sh --cc zig +./tests/run_tests.sh --cc tcc +``` + +### 擴展編譯器 +* **解析器 (Parser)**:`src/parser/` - 遞歸下降解析器。 +* **代碼生成 (Codegen)**:`src/codegen/` - 轉譯邏輯 (Zen C -> GNU C/C11)。 +* **標準庫 (Standard Library)**:`std/` - 使用 Zen C 本身編寫。 + +--- + +## 致謝與歸属 + +本項目使用了第三方庫。完整許可證文本可在 `LICENSES/` 目錄中找到。 + +* **[cJSON](https://github.com/DaveGamble/cJSON)** (MIT 許可證):用於語言服務器中的 JSON 解析和生成。 +* **[zc-ape](https://github.com/OEvgeny/zc-ape)** (MIT 許可證):由 [Eugene Olonov](https://github.com/OEvgeny) 開發的原版 Zen-C 實際上便攜的可執行文件 (APE) 端口。 +* **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (ISC 許可證):使 APE 成為可能納基礎庫。 -- 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(+) 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 b27b128f97596236a4ce6a3d9b40ef3dfad84d06 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 00:34:33 +0000 Subject: New standard lib (std/process.zc). --- README.md | 1 + README_ES.md | 1 + README_ZH_CN.md | 1 + README_ZH_TW.md | 1 + docs/std/README.md | 1 + docs/std/process.md | 57 ++++++++++++++++++++ examples/process/exec.zc | 20 +++++++ std.zc | 1 + std/process.zc | 133 ++++++++++++++++++++++++++++++++++++++++++++++ tests/std/test_process.zc | 26 +++++++++ 10 files changed, 242 insertions(+) create mode 100644 docs/std/process.md create mode 100644 examples/process/exec.zc create mode 100644 std/process.zc create mode 100644 tests/std/test_process.zc diff --git a/README.md b/README.md index 2745aba..75c4624 100644 --- a/README.md +++ b/README.md @@ -1106,6 +1106,7 @@ Zen C includes a standard library (`std`) covering essential functionality. | **`std/json.zc`** | JSON parsing and serialization. | [Docs](docs/std/json.md) | | **`std/stack.zc`** | LIFO Stack `Stack`. | [Docs](docs/std/stack.md) | | **`std/set.zc`** | Generic Hash Set `Set`. | [Docs](docs/std/set.md) | +| **`std/process.zc`** | Process execution and management. | [Docs](docs/std/process.md) | --- diff --git a/README_ES.md b/README_ES.md index 2735497..c5655e3 100644 --- a/README_ES.md +++ b/README_ES.md @@ -1106,6 +1106,7 @@ Zen C incluye una biblioteca estándar (`std`) que cubre las funcionalidades ese | **`std/json.zc`** | Parseo y serialización de JSON. | [Docs](docs/std/json.md) | | **`std/stack.zc`** | Pila LIFO `Stack`. | [Docs](docs/std/stack.md) | | **`std/set.zc`** | Conjunto Hash Genérico `Set`. | [Docs](docs/std/set.md) | +| **`std/process.zc`** | Ejecución y gestión de procesos. | [Docs](docs/std/process.md) | --- diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 52acb04..6fce2d2 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -1106,6 +1106,7 @@ Zen C 包含一个涵盖基本功能的标准库 (`std`)。 | **`std/json.zc`** | JSON 解析与序列化。 | [文档](docs/std/json.md) | | **`std/stack.zc`** | 后进先出栈 `Stack`。 | [文档](docs/std/stack.md) | | **`std/set.zc`** | 泛型哈希集合 `Set`。 | [文档](docs/std/set.md) | +| **`std/process.zc`** | 进程执行与管理。 | [文档](docs/std/process.md) | --- diff --git a/README_ZH_TW.md b/README_ZH_TW.md index b9b5511..fc9cef5 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -1106,6 +1106,7 @@ Zen C 包含一個涵蓋基本功能的標準庫 (`std`)。 | **`std/json.zc`** | JSON 解析與序列化。 | [文檔](docs/std/json.md) | | **`std/stack.zc`** | 後進先出棧 `Stack`。 | [文檔](docs/std/stack.md) | | **`std/set.zc`** | 泛型哈希集合 `Set`。 | [文檔](docs/std/set.md) | +| **`std/process.zc`** | 進程執行與管理。 | [文檔](docs/std/process.md) | --- diff --git a/docs/std/README.md b/docs/std/README.md index 16ffc74..3cbf8f8 100644 --- a/docs/std/README.md +++ b/docs/std/README.md @@ -8,6 +8,7 @@ - [Networking (Net)](./net.md) - TCP networking. - [Option](./option.md) - Optional values (Some/None). - [Path](./path.md) - File path manipulation. +- [Process](./process.md) - Process execution and management. - [Result](./result.md) - Error handling (Ok/Err). - [Queue](./queue.md) - FIFO queue (Ring Buffer). - [Set](./set.md) - Hash set implementation. diff --git a/docs/std/process.md b/docs/std/process.md new file mode 100644 index 0000000..31485ee --- /dev/null +++ b/docs/std/process.md @@ -0,0 +1,57 @@ +# Standard Library: Process (`std/process.zc`) + +The process module allows you to spawn and interact with child processes. + +## Usage + +```zc +import "std/process.zc" + +fn main() { + let output = Command::new("echo") + .arg("hello") + .output(); + + if (output.exit_code == 0) { + output.stdout.print(); + // Or access raw C string: output.stdout.c_str() + } +} +``` + +## Structs + +### Command + +A builder for spawning a process. + +```zc +struct Command { + program: String; + args: Vec; +} +``` + +#### Methods + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **new** | `Command::new(program: char*) -> Command` | Creates a new Command for the given program. | +| **arg** | `arg(self, arg: char*) -> Command*` | Adds an argument to the command. Returns the command pointer for chaining. | +| **output** | `output(self) -> Output` | Executes the command as a child process, waiting for it to finish and collecting all of its stdout. | +| **status** | `status(self) -> int` | Executes the command as a child process and returns the exit status code. Does not capture output (output goes to stdout/stderr). | + +### Output + +The output of a finished process. + +```zc +struct Output { + stdout: String; + exit_code: int; +} +``` + +#### Methods + +`Output` implements `Drop` to automatically free the captured `stdout` string. diff --git a/examples/process/exec.zc b/examples/process/exec.zc new file mode 100644 index 0000000..712a356 --- /dev/null +++ b/examples/process/exec.zc @@ -0,0 +1,20 @@ + +import "std/process.zc"; +import "std/string.zc"; + +fn main() { + "Executing 'ls -la' using std/process..."; + + let output = Command::new("ls") + .arg("-l") + .arg("-a") + .output(); + + if output.exit_code == 0 { + "--- Output ---"; + output.stdout.print(); + "--------------"; + } else { + !"Command failed with exit code: {output.exit_code}"; + } +} diff --git a/std.zc b/std.zc index 03bcd45..4793c11 100644 --- a/std.zc +++ b/std.zc @@ -18,4 +18,5 @@ import "./std/stack.zc" import "./std/queue.zc" import "./std/env.zc" import "./std/slice.zc" +import "./std/process.zc" diff --git a/std/process.zc b/std/process.zc new file mode 100644 index 0000000..d0b09a9 --- /dev/null +++ b/std/process.zc @@ -0,0 +1,133 @@ + +import "./core.zc"; +import "./vec.zc"; +import "./mem.zc"; +import "./string.zc"; +import "./option.zc"; + +raw { + void *_z_popen(char *command, char *type) { + return (void *)popen(command, type); + } + + int _z_pclose(void *stream) { + return pclose((FILE *)stream); + } + + char *_z_fgets(char *s, int size, void *stream) { + return fgets(s, size, (FILE *)stream); + } + + int _z_system(char *command) { + return system(command); + } +} + +extern fn _z_popen(command: char*, type: char*) -> void*; +extern fn _z_pclose(stream: void*) -> int; +extern fn _z_fgets(s: char*, size: int, stream: void*) -> char*; +extern fn _z_system(command: char*) -> int; + +struct Output { + stdout: String; + exit_code: int; +} + +struct Command { + program: String; + args: Vec; +} + +impl Command { + fn new(program: char*) -> Command { + return Command { + program: String::from(program), + args: Vec::new() + }; + } + + fn arg(self, arg: char*) -> Command* { + self.args.push(String::from(arg)); + return self; + } + + fn _build_cmd(self) -> String { + let cmd_str = self.program.substring(0, self.program.length()); + + for arg in &self.args { + let space = String::from(" "); + cmd_str.append(&space); + space.free(); + + cmd_str.append(arg); + } + + return cmd_str; + } + + fn output(self) -> Output { + let cmd_str = self._build_cmd(); + let cmd_c = cmd_str.c_str(); + + let fp = _z_popen(cmd_c, "r"); + + if (fp == 0) { + cmd_str.free(); + // TODO: Better error handling... + return Output { + stdout: String::from(""), + exit_code: -1 + }; + } + + let out = String::from(""); + let buf_size: usize = 1024; + let buf = (char*)malloc(buf_size); + + while (true) { + let res = _z_fgets(buf, (int)buf_size, fp); + if (res == 0) break; + + let chunk = String::from(buf); + out.append(&chunk); + chunk.free(); + } + + let code = _z_pclose(fp); + free(buf); + cmd_str.free(); + + return Output { + stdout: out, + exit_code: code + }; + } + + fn status(self) -> int { + let cmd_str = self._build_cmd(); + let code = _z_system(cmd_str.c_str()); + cmd_str.free(); + return code; + } + + fn free(self) { + self.program.free(); + + for s in &self.args { + s.free(); + } + self.args.free(); + } +} + +impl Drop for Command { + fn drop(self) { + self.free(); + } +} + +impl Drop for Output { + fn drop(self) { + self.stdout.free(); + } +} diff --git a/tests/std/test_process.zc b/tests/std/test_process.zc new file mode 100644 index 0000000..a3ba6ce --- /dev/null +++ b/tests/std/test_process.zc @@ -0,0 +1,26 @@ + +import "std/process.zc"; +import "std/string.zc"; + +test "process output" { + let cmd = Command::new("echo"); + cmd.arg("hello"); + + let out = cmd.output(); + + assert(out.exit_code == 0); + // echo usually outputs newline + assert(out.stdout.contains('h')); + assert(out.stdout.contains('e')); + assert(out.stdout.contains('l')); + assert(out.stdout.contains('o')); + + // out is dropped automatically + // cmd is dropped automatically +} + +test "process status" { + let cmd = Command::new("true"); // true command returns 0 + let status = cmd.status(); + assert(status == 0); +} -- cgit v1.2.3 From 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(-) 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(-) 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 6f95b74465f792ac9a600d5a41855f14173cd476 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 20:37:14 +0000 Subject: Fix portability issue macOS (vsnprintf) --- std/io.zc | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/std/io.zc b/std/io.zc index cfb9179..a5a7359 100644 --- a/std/io.zc +++ b/std/io.zc @@ -7,7 +7,10 @@ include // 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; + +// vsnprintf is problematic on macOS because it's a macro that expands to a builtin with a different signature +// so we wrap it in a C function to avoid the conflict +extern fn _z_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; @@ -18,6 +21,9 @@ def Z_EOF = -1; raw { void* _z_get_stdin(void) { return stdin; } int _z_fgetc(void* stream) { return fgetc((FILE*)stream); } + int _z_vsnprintf(char* str, size_t size, const char* fmt, va_list ap) { + return vsnprintf(str, size, fmt, ap); + } } extern fn _z_get_stdin() -> void*; @@ -27,16 +33,20 @@ fn format(fmt: char*, ...) -> char* { static let buffer: char[1024]; let ap: va_list; va_start(ap, fmt); - vsnprintf(buffer, 1024, fmt, ap); + + _z_vsnprintf(buffer, 1024, fmt, ap); va_end(ap); + return (char*)buffer; } fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = vsnprintf(buffer, size, fmt, ap); + + let ret = _z_vsnprintf(buffer, size, fmt, ap); va_end(ap); + return ret; } @@ -46,8 +56,10 @@ fn format_new(fmt: char*, ...) -> char* { let ap: va_list; va_start(ap, fmt); - vsnprintf(buffer, 1024, fmt, ap); + + _z_vsnprintf(buffer, 1024, fmt, ap); va_end(ap); + return buffer; } -- cgit v1.2.3 From 51638176265392c70ca2e0014de95867bac4991e Mon Sep 17 00:00:00 2001 From: Zuhaitz Date: Fri, 30 Jan 2026 21:57:35 +0000 Subject: Fix format error in READMEs --- README.md | 2 +- README_ES.md | 2 +- README_ZH_CN.md | 2 +- README_ZH_TW.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 001e590..ab51b83 100644 --- a/README.md +++ b/README.md @@ -607,7 +607,7 @@ Zen C supports a shorthand for prompting user input using the `?` prefix. - `? "Enter age: " (age)`: Prints prompt and scans input into the variable `age`. - Format specifiers are automatically inferred based on variable type. -```c +```zc let age: int; ? "How old are you? " (age); println "You are {age} years old."; diff --git a/README_ES.md b/README_ES.md index 999a7b4..d2cfbbb 100644 --- a/README_ES.md +++ b/README_ES.md @@ -606,7 +606,7 @@ Zen C soporta una abreviatura para solicitar entrada al usuario usando el prefij - `? "Ingresa la edad: " (edad)`: Imprime el prompt y escanea la entrada en la variable `edad`. - Los especificadores de formato se infieren automáticamente según el tipo de variable. -```c +```zc let edad: int; ? "¿Cuántos años tienes? " (edad); println "Tienes {edad} años."; diff --git a/README_ZH_CN.md b/README_ZH_CN.md index daa8a3d..2ac38a2 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -606,7 +606,7 @@ Zen C 支持使用 `?` 前缀进行用户输入提示的简写。 - `? "输入年龄: " (age)`: 打印提示并扫描输入到变量 `age` 中。 - 格式说明符会根据变量类型自动推断。 -```c +```zc let age: int; ? "你多大了? " (age); println "你 {age} 岁了。"; diff --git a/README_ZH_TW.md b/README_ZH_TW.md index 2707caa..13591cf 100644 --- a/README_ZH_TW.md +++ b/README_ZH_TW.md @@ -606,7 +606,7 @@ Zen C 支持使用 `?` 前綴進行用戶輸入提示的簡寫。 - `? "輸入年齡: " (age)`: 打印提示並掃描輸入到變量 `age` 中。 - 格式說明符會根據變量類型自動推斷。 -```c +```zc let age: int; ? "你多大了? " (age); println "你 {age} 歲了。"; -- cgit v1.2.3 From 422616a43b4f9b7659c96bfffd2f3095461dbea5 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 23:41:21 +0000 Subject: JSON serialization --- docs/std/json.md | 26 ++++++ docs/std/string.md | 4 + std/json.zc | 67 ++++++++++++++++ std/string.zc | 22 ++++++ tests/std/test_json_serialization.zc | 149 +++++++++++++++++++++++++++++++++++ 5 files changed, 268 insertions(+) create mode 100644 tests/std/test_json_serialization.zc diff --git a/docs/std/json.md b/docs/std/json.md index fba2ad8..ce35d64 100644 --- a/docs/std/json.md +++ b/docs/std/json.md @@ -51,6 +51,32 @@ Represents a node in a JSON document. - **`fn at(self, index: usize) -> Option`** Retrieves a value from an array by index. +#### Serialization + +- **`fn to_string(self) -> String`** + Serializes the JSON value to a string representation. + +- **`fn stringify(self, buf: String*)`** + Internal recursive serialization method that appends to a string buffer. + +**Example:** +```zc +let obj = JsonValue::object(); +obj.set("name", JsonValue::string("Alice")); +obj.set("age", JsonValue::number(30.0)); + +let json_str = obj.to_string(); +println "{json_str.c_str()}"; // {"name":"Alice","age":30} +json_str.free(); +obj.free(); +``` + +**Features:** +- Proper escaping of special characters: `\"`, `\\`, `\n`, `\t`, `\r`, `\b`, `\f` +- Numbers formatted with `%.15g` for precision +- Recursive serialization for nested objects and arrays +- Round-trip compatible with `parse()` + #### Memory Management - **`fn free(self)`** diff --git a/docs/std/string.md b/docs/std/string.md index 1f89e0f..a2f63f5 100644 --- a/docs/std/string.md +++ b/docs/std/string.md @@ -49,8 +49,12 @@ struct String { | Method | Signature | Description | | :--- | :--- | :--- | | **append** | `append(self, other: String*)` | Appends another string to this one. | +| **append_c** | `append_c(self, s: char*)` | Appends a C string literal. Uses value receiver. | +| **append_c_ptr** | `append_c_ptr(ptr: String*, s: char*)` | Appends a C string literal using pointer receiver for guaranteed mutation. | | **add** | `add(self, other: String*) -> String` | Concatenates this string and another into a new String. | +**Note:** When passing `String*` to functions that need to mutate, use `append_c_ptr` instead of `append_c` for reliable mutation. + ### Access & Query | Method | Signature | Description | diff --git a/std/json.zc b/std/json.zc index d373ab9..70f7cf2 100644 --- a/std/json.zc +++ b/std/json.zc @@ -457,3 +457,70 @@ impl Drop for JsonValue { self.free(); } } + +extern fn sprintf(s: char*, fmt: const char*, ...) -> int; + +impl JsonValue { + fn to_string(self) -> String { + let s = String::new(""); + self.stringify(&s); + return s; + } + + fn stringify(self, buf: String*) { + if (self.kind.tag == JsonType::JSON_NULL().tag) { + buf.append_c_ptr("null"); + } else if (self.kind.tag == JsonType::JSON_BOOL().tag) { + if (self.bool_val) { buf.append_c_ptr("true"); } else { buf.append_c_ptr("false"); } + } else if (self.kind.tag == JsonType::JSON_NUMBER().tag) { + let tmp: char[64]; + sprintf((char*)tmp, "%.15g", self.number_val); // Use %.15g for precision + buf.append_c_ptr((char*)tmp); + } else if (self.kind.tag == JsonType::JSON_STRING().tag) { + buf.append_c_ptr("\""); + let p = self.string_val; + let len = strlen(p); + for (let i = 0; i < len; i = i + 1) { + let c = p[i]; + if (c == '"') buf.append_c_ptr("\\\""); + else if (c == '\\') buf.append_c_ptr("\\\\"); + else if (c == '\n') buf.append_c_ptr("\\n"); + else if (c == '\t') buf.append_c_ptr("\\t"); + else if (c == '\r') buf.append_c_ptr("\\r"); + else if (c == '\b') buf.append_c_ptr("\\b"); + else if (c == '\f') buf.append_c_ptr("\\f"); + else { + let tmp: char[2]; tmp[0] = c; tmp[1] = 0; + buf.append_c_ptr((char*)tmp); + } + } + buf.append_c_ptr("\""); + } else if (self.kind.tag == JsonType::JSON_ARRAY().tag) { + buf.append_c_ptr("["); + let v = self.array_val; + for (let i: usize = 0; i < v.length(); i = i + 1) { + if (i > 0) buf.append_c_ptr(","); + let item = v.get(i); + (*item).stringify(buf); + } + buf.append_c_ptr("]"); + } else if (self.kind.tag == JsonType::JSON_OBJECT().tag) { + buf.append_c_ptr("{{"); + let m = self.object_val; + let first = true; + for (let i: usize = 0; i < m.capacity(); i = i + 1) { + if (m.is_slot_occupied(i)) { + if (!first) buf.append_c_ptr(","); + first = false; + let key = m.key_at(i); + buf.append_c_ptr("\""); + buf.append_c_ptr(key); // Assuming keys are simple for now, but really should escape them too + buf.append_c_ptr("\":"); + let val = m.val_at(i); + val.stringify(buf); + } + } + buf.append_c_ptr("}"); + } + } +} diff --git a/std/string.zc b/std/string.zc index fe5b0ad..0bc9539 100644 --- a/std/string.zc +++ b/std/string.zc @@ -55,6 +55,28 @@ impl String { } } + fn append_c(self, s: char*) { + if (self.vec.len > 0) { + self.vec.len = self.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + self.vec.push(s[i]); + } + self.vec.push(0); + } + + fn append_c_ptr(ptr: String*, s: char*) { + if (ptr.vec.len > 0) { + ptr.vec.len = ptr.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + ptr.vec.push(s[i]); + } + ptr.vec.push(0); + } + fn add(self, other: String*) -> String { let new_s = String::from(self.c_str()); new_s.append(other); diff --git a/tests/std/test_json_serialization.zc b/tests/std/test_json_serialization.zc new file mode 100644 index 0000000..9fd5b32 --- /dev/null +++ b/tests/std/test_json_serialization.zc @@ -0,0 +1,149 @@ +import "std/json.zc" +import "std/io.zc" + +test "primitives" { + // Null + let v = JsonValue::null(); + let s = v.to_string(); + let expected = String::from("null"); + if (!s.eq(&expected)) { + panic("Null serialization failed"); + } + expected.free(); + s.free(); + + // Bool True + v = JsonValue::bool(true); + s = v.to_string(); + expected = String::from("true"); + if (!s.eq(&expected)) { + panic("Bool true serialization failed"); + } + expected.free(); + s.free(); + + // Bool False + v = JsonValue::bool(false); + s = v.to_string(); + expected = String::from("false"); + if (!s.eq(&expected)) { + panic("Bool false serialization failed"); + } + expected.free(); + s.free(); + + // Number Int + v = JsonValue::number(123.0); + s = v.to_string(); + expected = String::from("123"); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("Number 123 serialization failed"); + } + expected.free(); + s.free(); + + // Number Float + v = JsonValue::number(12.5); + s = v.to_string(); + expected = String::from("12.5"); + if (!s.eq(&expected)) { + panic("Number 12.5 serialization failed"); + } + expected.free(); + s.free(); + + // String Simple + v = JsonValue::string("hello"); + s = v.to_string(); + expected = String::from("\"hello\""); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("String hello serialization failed"); + } + expected.free(); + s.free(); + + // String Escaped + v = JsonValue::string("hello \"world\""); + s = v.to_string(); + expected = String::from("\"hello \\\"world\\\"\""); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("String escaped serialization failed"); + } + expected.free(); + s.free(); +} + +test "array" { + let v = JsonValue::array(); + v.push(JsonValue::number(1.0)); + v.push(JsonValue::bool(true)); + v.push(JsonValue::string("a")); + + let s = v.to_string(); + let expected = String::from("[1,true,\"a\"]"); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("Array serialization failed"); + } + expected.free(); + s.free(); +} + +test "object" { + let v = JsonValue::object(); + v.set("key", JsonValue::string("value")); + + let s = v.to_string(); + // Round trip verification to avoid parser bug with literals + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Object round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip not object"); + + let val_opt = (*parsed).get_string("key"); + if (val_opt.is_none()) panic("Round trip missing 'key'"); + + let val_str = val_opt.unwrap(); + if (strcmp(val_str, "value") != 0) panic("Round trip wrong value"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} + +test "nested" { + // {"arr":[1,2]} + let v = JsonValue::object(); + let arr = JsonValue::array(); + arr.push(JsonValue::number(1.0)); + arr.push(JsonValue::number(2.0)); + v.set("arr", arr); + + let s = v.to_string(); + + // Round trip + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip type mismatch"); + + let arr_opt = (*parsed).get_array("arr"); + if (arr_opt.is_none()) panic("Round trip missing arr"); + + let arr_ptr = arr_opt.unwrap(); + if (!(*arr_ptr).is_array()) panic("Inner not array"); + if ((*arr_ptr).len() != 2) panic("Wrong array length"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} \ No newline at end of file -- cgit v1.2.3 From 03a6a57f500ee4230ce2cee887866b3850ed7ed9 Mon Sep 17 00:00:00 2001 From: Zuhaitz Méndez Fernández de Aránguiz Date: Fri, 30 Jan 2026 23:49:49 +0000 Subject: Fix portability issue macOS --- std/json.zc | 2 -- 1 file changed, 2 deletions(-) diff --git a/std/json.zc b/std/json.zc index 70f7cf2..9f5cf73 100644 --- a/std/json.zc +++ b/std/json.zc @@ -458,8 +458,6 @@ impl Drop for JsonValue { } } -extern fn sprintf(s: char*, fmt: const char*, ...) -> int; - impl JsonValue { fn to_string(self) -> String { let s = String::new(""); -- 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 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(+) 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(-) 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(-) 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 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(-) 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(-) 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(-) 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(-) 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